TCP套接字编写,多进程多线程版本 Linux网络通信( 五 )


注意: 因为数据是要发送到网络中,所以要将主机序列的端口号转为网络序列的端口号
绑定端口号,需要填充struct sockaddr_in这个结构体 , 里面有协议家族,端口号和IP,端口号根据用户传参进行填写,IP直接绑定INADDR_ANY,具体代码如下:
bool TcpServerInit(){// 绑定struct sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(_port);local.sin_addr.s_addr = INADDR_ANY;if (bind(_listen_sock, (struct sockaddr*)&local, sizeof(local)) < 0){cout << "bind fail" << endl;return false;}cout << "bind success" << endl;}将套接字设置为监听状态这里就需要用的listen这个接口,让套接字处于监听状态 , 然后可以去监听连接的到来代码也很简单,具体如下:
bool TcpServerInit(){// 将套接字设置为监听状态if (listen(_listen_sock, BACK_LOG) < 0){cout << "listen fail" << endl;return false;}cout << "listen success" << endl;}循环获取连接听套接字通过accept获取连接,一次获取连接失败不要直接将服务端关闭,而是重新去获取连接就好,因为获取一个连接失败而直接关闭服务端,带来的损失是很大的,所以只需要重新获取连接即可 , 返回的用于通信套接字记录下来,进行通信,然后可以用多种方式为各种连接连接提供服务,具体服务方式后面细说,先看获取连接的一部分代码:
void loop(){struct sockaddr_in peer;// 获取远端端口号和ip信息socklen_t len = sizeof(peer);while (1){// 获取链接// sock 是进行通信的一个套接字_listen_sock 是进行监听获取链接的一个套接字int sock = accept(_listen_sock, (struct sockaddr*)&peer, &len);if (sock < 0){cout << "accept fail, continue accept" << endl;continue;}// 提供服务 service 后面介绍}}客户端整体框架和服务端一样 , 封装一个类描述 , 类成员有服务端ip、服务端绑定的端口号以及自身套接字,代码如下:
class TcpClient{public:TcpClient(string ip, int port):_server_ip(ip),_server_port(port),_sock(-1){}~TcpClient(){if (_sock >= 0) close(_sock);}private:string _server_ip;int _server_port;int _sock;};客户端初始化客户端的初始化只需要创建套接字即可,不需要绑定端口号,发起连接请求的时候 , 会自动给客户端分配一个端口号 。创建套接字和服务端是一样的,代码如下:
bool TcpClientInit(){// 创建套接字_sock = socket(AF_INET, SOCK_STREAM, 0);if (_sock < 0){cout << "socket creat fail" << endl;return false;}cout << "socket creat succes, sock: " << _sock << endl;return true;}客户端启动发起连接请求使用connect函数,想服务端发起连接请求,注意,调用这个函数之前,需要先填充好服务端的信息,有协议家族、端口号和IP,请求连接失败直接退出进程,重新启动进程即可,连接成功之后就可以像服务端发起各自的服务请求(后面介绍),代码如下:
void TcpClientStart(){// 连接服务器struct sockaddr_in peer;peer.sin_family = AF_INET;peer.sin_port = htons(_server_port);peer.sin_addr.s_addr = inet_addr(_server_ip.c_str());if (connect(_sock, (struct sockaddr*)&peer, sizeof(peer)) < 0){// 连接失败cerr << "connect fail" <<endl;exit(-1);}cout << "connect success" << endl;Request();// 下面介绍}发起服务请求请求很简单,只需要让用户输入字符串请求 , 然后将请求通过write(send也可以)发送过去,然后创建一个缓冲区,通过read(recv也可以)读取服务端的响应 , 这里需要着重介绍一下read的返回值

  1. 大于0:实际读取的字节数
  2. 等于0:读到了文件末尾,说明对端关闭 , 用在服务端就是客户端关闭,用在客户端就是服务端关闭了,客户端可以直接退出
  3. 小于0:说明读取失败
void Request(){string msg;while (1){cout << "Please Enter# ";getline(cin, msg);write(_sock, msg.c_str(), msg.size());char buf[256];ssize_t size = read(_sock, buf, sizeof(buf)-1);if (size <= 0){cerr << "read error" << endl;exit(-1);}buf[size] = 0;cout << buf << endl;}}不同版本的服务端服务代码多进程版本思路: 为了给不同的连接提供服务,所以我们需要让父进程去不断获取连接 , 获取连接后,让父进程创建一个子进程去为这个获取到的连接提供服务,那么问题来了,子进程去服务连接,父进程是否需要等待子进程?按常理来说,是需要的,如果不等待的话,子进程退出,子进程的资源就没有人回收,就变成僵尸进程了,如果父进程等待子进程的话,父进程就需要阻塞在哪,无法去获取到新的连接,这也是不完全可行的,所以就有了一下两种解决方案:

推荐阅读