套接字编程
端口号
端口号(port)是传输层协议的内容,端口号是一个2字节16位的整数
端口号用来标识一台主机内的唯一一个进程,公网IP标识全网内唯一一个主机,ip+port:标识全网内的唯一一个进程
一个端口号只能被一个进程占用,而单个进程可以占用多个端口号
进程pid与port的关系
在OS中,并不是所有的进程都需要进行网络通信,pid标识系统里的一个进程,port标识进行网络通信的一个进程
pid是系统级别的概念,port是网络的概念
网络字节序
网络数据流具有大端和小端之分,为了统一网络中数据的读取,规定网络序列都为大端
发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;
接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存;
因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址.
TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节.
不管这台主机是大端机还是小端机, 都会按照这个TCP/IP规定的网络字节序来发送/接收数据;
主机字节序与网络字节序转换接口
#include <arpa/inet.h>
主机到网络
uint32 t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
网络到主机
uint32_t ntohl(uint32_t netlong);
uintl6 t ntohs(uint16 t netshort);
socket套接字
socket是对TCP/IP协议的封装,它的出现只是使得程序员更方便地使用TCP/IP协议栈而已。socket本身并不是协议,它是应用层与TCP/IP协议族通信的中间软件抽象层,是一组调用接口(TCP/IP网络的API函数)
sockaddr结构
socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4、IPv6,然而, 各种网络协议的地址格式并不相同
struct sockaddr
typedef unsigned short sa_family_t;//两字节
struct sockaddr {
sa_family_t sa_family; /* address family, AF_xxx */
char sa_data[14]; /* 14 bytes of protocol address */
};
ipv4的sockaddr_in:
typedef uint16_t in_port_t; //端口
typedef uint32_t in_addr_t; //ip
struct in_addr
{
in_addr_t s_addr;
};
struct sockaddr_in
{
__SOCKADDR_COMMON (sin_);//协议家族 AF_INET
in_port_t sin_port; //端口
struct in_addr sin_addr; //ip
//用0填充
unsigned char sin_zero[sizeof (struct sockaddr) -
__SOCKADDR_COMMON_SIZE -
sizeof (in_port_t) -
sizeof (struct in_addr)];
};
创建 socket文件描述符
int socket(int domain, int type, int protocol)
参数:
domain:协议家族,例如 AF_INET,AF_INET6
type:服务类型,包括:
SOCK_DGRAM:用户数据报套接字(UDP)
SOCK_STREAM:流式套接字(TCP)
protocol:协议类别默认为0,该参数可以由前两个推断出来
返回值:
返回文件描述符,返回值小于0,创建失败
通过文件描述符将进程与网卡进行绑定,网络也是文件,可以向网卡写入数据,读取数据
绑定端口号
int bind(int socket, const struct sockaddr *address, socklen_t address_len);
参数:
socket:套接字的文件描述符,socket的返回值
address:当前绑定的地址信息
address_len:地址信息的大小
返回值:
成功返回0,失败返回-1
让创建的套接字绑定ip和port
注意:port需要发送到网络中,需要设置为网络序列
UDP
UDP接收接口
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
参数:
sockfd:套接字的文件描述符,从哪读
buf:缓冲区,接收到的数据放到缓冲区当中
len:期望读到的大小
flags:0表示阻塞接收
src_addr:消息发送方的地址信息结构,为出参,由recvfrom填充
addrlen:输出型参数,recvfrom函数,地址信息长度为多少,返回发送方实际的长度
返回值:
返回实际读到字符的长度,发生错误返回-1,若对端关闭socket描述符,返回0
UDP不面向连接,所以在接收信息时,要拿到对端数据与对端信息
注意:
在返回的src_addr参数中端口号为网络字节序格式的需要转化
ip为四字节32位的,需要转为字符串风格的
udp_server.hpp
1 #include<iostream>
2 #pragma once
3 using namespace std;
4 #include<string>
5 #include<sys/types.h>
6 #include<sys/socket.h>
7 #include<cstring>
8 #include<netinet/in.h>
9 #include<arpa/inet.h>
10 #define DEFAULT 8081
11 #define SIZE 128
12 class UdpServer
13 {
14 private:
15 string ip;
16 int port;//端口号
17 int sockfd;
18 public:
19 UdpServer(string _ip,int _port=DEFAULT)
20 :ip(_ip)
21 ,port(_port)
22 ,sockfd(-1)
23 {
24 }
25 bool InitUdpServer()
26 {
27 sockfd=socket(AF_INET,SOCK_DGRAM,0);
28 if(sockfd<0)
29 {
30 cerr<<"socket error"<<endl;
31 return false;
32 }
33 cout<<"socket create success:sockfd:"<<sockfd<<endl;
34 struct sockaddr_in local;
35 memset(&local,'\0',sizeof(local));
36 local.sin_family=AF_INET;
37 local.sin_port=htons(port);
38 local.sin_addr.s_addr=inet_addr(ip.c_str());//将点分十进制string转为uint32_t
39 if(bind(sockfd,(struct sockaddr*) &local,sizeof(local))<0)
40 {
41 cerr<<"bind error"<<endl;
42 return false;
43 }
44 cout<<"bind success"<<endl;
45 return true;
46 }
47 void start()
48 {
49 char buffer[SIZE];
50 for(;;)
51 {
52 struct sockaddr_in peer;
53 socklen_t len=sizeof(peer);
54 //必须传入大小,输出型参数传回实际的大小
55 ssize_t size=recvfrom(sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)& peer,&len);
56 if(size>0)
57 {
58 buffer[size]=0;
59 int _port=ntohs(peer.sin_port);
60 string _ip=inet_ntoa(peer.sin_addr);
61 cout<<_ip<<":"<<_port<<"#"<<buffer<<endl;
62 }
63 else
64 {
65 cerr<<"recvfrom error"<<endl;
66 }
67
68 }
69
70 }
71 ~UdpServer()
72 {
73
74 }
75 };
udp_server.c
1 #include"udp_server.hpp"
2 // ./udp_server port
3 int main(int argc,char* argv[])
4 {
5 if(argc!=2)
6 {
7 cerr<<"USE:./udp_server port"<<endl;
8 return 1;
9 }
10 string ip="127.0.0.1";//localhost 标识本主机 本地环回
11 int port=atoi(argv[1]);
12 UdpServer* svr=new UdpServer(ip,port);
13 svr->InitUdpServer();
14 svr->start();
15 return 0;
16 }
netstat -nlup查看当前网络状态
UDP发送接口
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
参数:
sockfd:套接字描述符
buf:待发送的数据
len:数据长度,期望发送
flags:标志位,0:阻塞发送
dest_addr:目的地址信息,消息接收方
addrlen:地址信息长度
返回值:
成功返回实际发出的字节长度,失败返回-1
udp服务器
udp_client.hpp
1 #include<iostream>
2 using namespace std;
3 #include<string>
4 #include<sys/socket.h>
5 #include<sys/types.h>
6 #include<cstring>
7 #include<arpa/inet.h>
8 #include<netinet/in.h>
9 class UdpClient
10 {
11 private:
12 int sockfd;
13 string server_ip;
14 int server_port;
15 public:
16 UdpClient(string ip,int port)
17 :server_ip(ip)
18 ,server_port(port)
19 {
20
21 }
22 bool InitUdpClient()
23 {
24 sockfd=socket(AF_INET,SOCK_DGRAM,0);
25 if(sockfd<0)
26 {
27 cerr<<"socket error!"<<endl;
28 return 1;
29 }
30 //客户端不需要绑定
31 return true;
32 }
33 void Start()
34 {
35 //目的地址信息
36 struct sockaddr_in peer;
37 memset(&peer,0,sizeof(peer));
38 peer.sin_family=AF_INET;
39 peer.sin_port=htons(server_port);
40 peer.sin_addr.s_addr=inet_addr(server_ip.c_str());
41 string msg;
42 for(;;)
43 {
44 cout<<"please Enter#";
45 cin>>msg;
46 sendto(sockfd,msg.c_str(),msg.size(),0,(struct sockaddr*)& peer,sizeof(peer));
47 }
48
49 }
50
51 };
udp_client.c
1 #include"udp_client.hpp"
2 // ./udp_client server_ip server_port
3 int main(int argc,char* argv[])
4 {
5 if(argc!=3)
6 {
7 cerr<<"Usege:./udp_client server_ip server_port"<<endl;
8 return 1;
9 }
10 string ip=argv[1];
11 int port=atoi(argv[2]);
12 UdpClient *udp=new UdpClient(ip,port);
13 udp->InitUdpClient();
14 udp->Start();
15 return 0;
16 }
这样就可以实现客户端与服务器通信了
如果要让外网访问该服务器
下面介绍tcp相关套接字接口
TCP
TCP的监听接口
int listen(int sockfd, int backlog);
参数:
sockfd:侦听套接字,socket的返回值
backlog:全连接队列的最大长度
返回值:
成功返回0,失败返回-1
内核为任何一个监听套接字维护着两个队列
1.未完成连接队列:已由某个客户发出到达服务器,而服务器正在等待完成相应的TCP三次握手的过程,这些套接字处于SYN_RCVD状态,该队列的大小由操作系统提供的算法生成
2.已完成连接队列:未连接队列中完成了三次握手的就将该连接移到该队列队尾,这些套接字处ESTABLISHED状态,该队列大小为backlog+1
当进程下次调用accept的时候,已完成连接队列的队头将返回给调用进程,如果队列为空,那么进程将睡眠,直到有新的数据项到来才唤醒,这样accept到的连接都是已经建立连接成功的,保证先建立连接再进行通信
TCP获取连接接口
TCP在通信前需要先获取连接
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数:
sockfd:监听套接字的文件描述符
addr:对端地址信息,客户端的地址信息
addrlen:对端地址信息,客户端的地址信息长度
返回值:
成功,返回的是新创建出来的套接字文件描述符
该返回的文件描述符是真正为客户端提供服务的,原来创建的监听套接字是为了获取新的连接
connect连接
TCP客户端使用connect函数与服务器发起连接
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
参数:
sockfd:客户端的sock套接字
addr:要连接的目的服务器的sockaddr信息
addrlen:addr的大小
如果成功,则系统随机指定端口号,动态设置
服务器要维护管理连接,先描述再组织,需要时间和空间上的成本,连接也不是越多越好
tcp实现echo服务器
tcp_server.hpp
1 #pragma once
2 #include<unistd.h>
3 #include<cstdlib>
4 #include<cstring>
5 #include<iostream>
6 #include<sys/socket.h>
7 #include<sys/types.h>
8 #include<arpa/inet.h>
9 #include<sys/fcntl.h>
10 #include<netinet/in.h>
11 using namespace std;
12 #define BACK_LOG 4
13 #define DFL_PORT 8081
14 class TcpServer
15 {
16 private:
17 int _port;
18 int listen_sock;
19 public:
20 TcpServer(int port=DFL_PORT)
21 :_port(port)
22 ,listen_sock(-1)
23 {
}
24 void InitServer()
25 {
26 listen_sock=socket(AF_INET,SOCK_STREAM,0);
27 if(listen_sock<0)
28 {
29 cerr<<"socket error"<<endl;
30 exit(2);
31 }
32 struct sockaddr_in local;
33 memset(&local,0,sizeof(local));
34 local.sin_family=AF_INET;
35 local.sin_port=htons(_port);
36 cout<<_port<<endl;
37 local.sin_addr.s_addr=INADDR_ANY;
38 //弱化IP强调端口,凡是端口号是服务器的我都处理
39 //inet_addr("192.0.0.1");将点分十进制字符串风格的ip转化成四字节的整数风格的ip地址
40
41 if(bind(listen_sock,(struct sockaddr*)& local,sizeof(local))<0)
42 {
43 cerr<<"bind error"<<endl;
44 exit(3);
45 }
46 //监听
47 if(listen(listen_sock,BACK_LOG)<0)
48 {
49 cerr<<"listen error"<<endl;
50 exit(4);
51 }
52 }
53 void Loop()
54 {
55 struct sockaddr_in peer;
56 for(;;)
57 {
58 socklen_t len=sizeof(peer);
59 int sock=accept(listen_sock,(struct sockaddr*)& peer,&len);
60 if(sock<0)
61 {
62 cout<<"accept error continue"<<endl;
63 continue;
64 }
65 string ip=inet_ntoa(peer.sin_addr);
66 int port=ntohs(peer.sin_port);
67 cout<<"get a new link["<<ip<<"]:"<<port<<endl;
68 Service(sock,ip,port);
69
70 }
71 }
72 //echo
73 void Service(int sock,string ip,int port)
74 {
75 char buffer[1024];
76 while(true)
77 {
78 ssize_t size=read(sock,buffer,sizeof(buffer)-1);//阻塞读
79 if(size>0)
80 {
81 buffer[size]=0;
82 cout<<"receive "<<ip<<":"<<port<<"#"<<buffer<<endl;
83 //TCP是全双工的读写都是一个文件描述符
84 write(sock,buffer,size);
85 }
86 else if(size==0)//对端关闭
87 {
88 cout<<ip<<":"<<port<<"close!"<<endl;
89 break;
90 }
91 else
92 {
93 cerr<<sock<<"read error"<<endl;
94 break;
95 }
96 }
97 close(sock);
98 }
99 ~TcpServer()
100 {
101 if(listen_sock>=0)
102 {
103 close(listen_sock);
104 }
105 }
106 };
tcp_server.c
1 #include"tcp_server.hpp"
2 void Usage(string proc)
3 {
4 cout<<"Usage: "<<proc<<" port"<<endl;
5 }
6 int main(int argc,char* argv[])
7 {
8 if(argc!=2)
9 {
10 Usage(argv[0]);
11 exit(1);
12 }
13 TcpServer ts(atoi(argv[1]));
14 ts.InitServer();
15 cout<<"success"<<endl;
16 ts.Loop();
17 return 0;
18 }
tcp_client.hpp
1 #pragma once
2 #include<iostream>
3 #include<cstring>
4 using namespace std;
5 #include<string>
6 #include<unistd.h>
7 #include<netinet/in.h>
8 #include<arpa/inet.h>
9 #include<sys/socket.h>
10 #include<sys/types.h>
11 class TcpClient
12 {
13 private:
14 string svr_ip;
15 int svr_port;
16 int sock;
17 public:
18 TcpClient(string ip,int port)
19 :svr_ip(ip)
20 ,svr_port(port)
21 ,sock(-1)
22 {
}
23 void InitTcpClient()
24 {
25 sock=socket(AF_INET,SOCK_STREAM,0);
26 if(sock<0)
27 {
28 cerr<<"socket error"<<endl;
29 exit(2);
30 }
31 }
32 void Start()
33 {
34 struct sockaddr_in peer;
35 memset(&peer,0,sizeof(peer));
36 peer.sin_family=AF_INET;
37 peer.sin_port=htons(svr_port);
38 peer.sin_addr.s_addr=inet_addr(svr_ip.c_str());
39 if(connect(sock,(struct sockaddr*) &peer,sizeof(peer))==0)
40 {
41 //连接成功
42 cout<<"connect success ..."<<endl;
43 Request(sock);
44 }
45 else
46 {
47 //连接失败
48 cout<<"connect failed ..."<<endl;
49 }
50 }
51 void Request(int sock)
52 {
53 string message;
54 char buffer[1024];
55 while(true)
56 {
57 cout<<"Please Enter# ";
58 cin>>message;
59 write(sock,message.c_str(),message.size());
60 ssize_t n=read(sock,buffer,sizeof(buffer)-1);
61 if(n>0)
62 {
63 buffer[n]=0;
64 }
65 cout<<"server echo# "<<buffer<<endl;
66 }
67 }
68 ~TcpClient()
69 {
70 if(sock>=0)
71 {
72 close(sock);
73 }
74 }
75
76 };
tcp_client.c
1 #include"tcp_client.hpp"
2 void Usage(string proc)
3 {
4 cout<<"Usage: "<<proc<<" server_ip server_port"<<endl;
5 }
6 int main(int argc,char* argv[])
7 {
8 if(argc!=3)
9 {
10 Usage(argv[0]);
11 exit(1);
12 }
13 TcpClient tp(argv[1],atoi(argv[2]));
14 tp.InitTcpClient();
15 tp.Start();
16 return 0;
17 }
运行结果:
该服务器为单执行流,server会一直循环等待client输入,client断开后break再获取下一个连接是串行执行的,真正的服务器程序肯定是多执行流的,同时向多个客户端提供服务
多执行流tcp服务器
多进程版
版本一:
创建子进程,让子进程提供服务,父进程不断获取连接,为了不阻塞等待子进程,忽略SIGCHLD信号
void Loop()
55 {
56 signal(SIGCHLD,SIG_IGN);
57 //忽略SIGCHLD信号,子进程退出会自动释放掉进程资源,避免成为僵尸进程
58 struct sockaddr_in peer;
59 for(;;)
60 {
61 socklen_t len=sizeof(peer);
62 int sock=accept(listen_sock,(struct sockaddr*)& peer,&len);
63 if(sock<0)
64 {
65 cout<<"accept error continue"<<endl;
66 continue;
67 }
68 string ip=inet_ntoa(peer.sin_addr);
69 int port=ntohs(peer.sin_port);
70 cout<<"get a new link["<<ip<<"]:"<<port<<endl;
71
72 pid_t id=fork();
73 if(id==0)
74 {
75 //子进程提供服务
76 Service(sock,ip,port);
77 }
78 //父进程获取下一个连接
79 }
80 }
方法二:
创建孙进程,让孙进程执行服务,父进程退出,孙进程成为孤儿进程,被操作系统领养
void Loop()
56 {
57 struct sockaddr_in peer;
58 for(;;)
59 {
60 socklen_t len=sizeof(peer);
61 int sock=accept(listen_sock,(struct sockaddr*)& peer,&len);
62 if(sock<0)
63 {
64 cout<<"accept error continue"<<endl;
65 continue;
66 }
67
68 pid_t id=fork();
69 if(id==0)
70 {
71 //关闭父进程的监听sock
72 close(listen_sock);
73 if(fork()>0)
74 {
75 //子进程
76 exit(0);
77 }
78 //孙进程提供服务
79 string ip=inet_ntoa(peer.sin_addr);
80 int port=ntohs(peer.sin_port);
81 cout<<"get a new link["<<ip<<"]:"<<port<<endl;
82 Service(sock,ip,port);
83 exit(0);
84 }
85 //父进程
86 close(sock);//已经被孙进程继承,关闭sock
87 waitpid(id,nullptr,0);//等待,回收子进程
88 }//父进程继续获取连接
89 }
多线程版
方法三:
创建线程,创建后线程分离并提供服务,主线程就不需要等待了继续accept下一个连接
class Pragma
56 {
57 public:
58 int _sock;
59 string _ip;
60 int _port;
61 Pragma(int sock,string ip,int port)
62 :_sock(sock)
63 ,_ip(ip)
64 ,_port(port)
65 {
}
66 };
67 static void* HandlerRequest(void* arg)
68 {
69 Pragma* p=(Pragma*)arg;
70 pthread_detach(pthread_self());
71 Service(p->_sock,p->_ip,p->_port);//静态成员函数
72 close(p->_sock);
73 delete p;
74 return nullptr;
75 }
76 void Loop()
77 {
78 struct sockaddr_in peer;
79 for(;;)
80 {
81 socklen_t len=sizeof(peer);
82 int sock=accept(listen_sock,(struct sockaddr*)& peer,&len);
83 if(sock<0)
84 {
85 cout<<"accept error continue"<<endl;
86 continue;
87 }
88
89 pthread_t tid;
90 string ip=inet_ntoa(peer.sin_addr);
91 int port=ntohs(peer.sin_port);
92 Pragma* p=new Pragma(sock,ip,port);
93 pthread_create(&tid,nullptr,HandlerRequest,p);//主线程继续accept
94 }
95 }
这个缺点也很明显:每次有连接到来才开始创建线程,创建线程开销较大,效率低
方法四:
线程池版本:tcp服务器进入Loop循环是初始化线程池,线程池里面线程等待任务的到来,获取连接后就push任务到线程池,供线程池"消费"
void Loop()
81 {
82 tp->InitThreadPool();
83 struct sockaddr_in peer;
84 for(;;)
85 {
86 socklen_t len=sizeof(peer);
87 int sock=accept(listen_sock,(struct sockaddr*)& peer,&len);
88 if(sock<0)
89 {
90 cout<<"accept error continue"<<endl;
91 continue;
92 }
93 string ip=inet_ntoa(peer.sin_addr);
94 int port=ntohs(peer.sin_port);
95 Task t(sock,ip,port);
96 tp->Push(t);
97 }
98 }
Threadpool.hpp
1 #include<iostream>
2 using namespace std;
3 #include<pthread.h>
4 #include<queue>
5 #define NUM 5
6 template<class T>
7 class ThreadPool
8 {
9 private:
10 queue<T> task_queue;
11 int thread_num;
12 pthread_mutex_t lock;
13 pthread_cond_t cond;
14 public:
15 ThreadPool(int num=NUM)
16 :thread_num(num)
17 {
18 pthread_mutex_init(&lock,NULL);
19 pthread_cond_init(&cond,NULL);
20 }
21 void LockQueue()
22 {
23 pthread_mutex_lock(&lock);
24 }
25 void UnlockQueue()
26 {
27 pthread_mutex_unlock(&lock);
28 }
29 void waitcond()
30 {
31 pthread_cond_wait(&cond,&lock);
32 }
33 void wakeup()
34 {
35 pthread_cond_signal(&cond);
36 }
37 bool isEmptyThreadPool()
38 {
39 return task_queue.size()==0;
40 }
41
42 static void* Routine(void* arg)
43 {
44 pthread_detach(pthread_self());
45 ThreadPool* This=(ThreadPool*) arg;
46 while(true)
47 {
48 This->LockQueue();
49 if(This->isEmptyThreadPool())
50 {
51 This->waitcond();
52 }
53 T t;
54 This->Pop(t);
55 This->UnlockQueue();
56 cout<<"thread:"<<pthread_self()<<"run:";
57 t.run();
58 }
59 }
60 void Push(T& in)
61 {
62 LockQueue();
63 task_queue.push(in);
64 UnlockQueue();
65 wakeup();
66 }
67 void Pop(T& out)
68 {
69 out=task_queue.front();
70 task_queue.pop();
71 }
72 void InitThreadPool()
73 {
74 pthread_t tid;
75 for(int i=0;i<thread_num;i++)
76 {
77 pthread_create(&tid,NULL,Routine,this);
78 }
79 }
80 ~ThreadPool()
81 {
82 pthread_cond_destroy(&cond);
83 pthread_mutex_destroy(&lock);
84 }
85 };
Task.hpp
1 #pragma once
2 #include<iostream>
3 #include<string>
4 #include<unistd.h>
5 using namespace std;
6 #include<pthread.h>
7 class Task
8 {
9 private:
10 int sock;
11 string ip;
12 int port;
13 public:
14 Task(){
}
15 Task(int _sock,string _ip,int _port)
16 :sock(_sock)
17 ,ip(_ip)
18 ,port(_port)
19 {
}
20 void run()
21 {
22 auto f = [](int sock,string ip,int port)
23 {
24 char buffer[1024];
25 while(true)
26 {
27 ssize_t size=read(sock,buffer,sizeof(buffer)-1);//阻塞读
28 if(size>0)
29 {
30 buffer[size]=0;
31 cout<<"receive "<<ip<<":"<<port<<"#"<<buffer<<endl;
32 //TCP是全双工的读写都是一个文件描述符
33 write(sock,buffer,size);
34 }
35 else if(size==0)//对端关闭
36 {
37 cout<<ip<<":"<<port<<"close!"<<endl;
38 break;
39 }
40 else
41 {
42 cerr<<sock<<"read error"<<endl;
43 break;
44 }
45 }
46 close(sock);
47 };
48 f(sock,ip,port);
49 }
50 };
文章评论