接上一篇TCP协议的通信小程序与函数接口
先来复习一下TCP通信的规则,客户端和服务端先建立一个套接字并绑定了地址信息,然后服务端去侦听客户端发送的连接请求(历经三次握手),请求成功后就拷贝信息到新的套接字结构体中,让他和客户端进行无缝连接,完成通信。
多个端口通信的问题所在
当我们只有一个客户端与服务端发送通信的时候,我们可以看到通信是正常进行的。问题就在于我们再加了一个客户端的时候,就出现了这种情况:
在服务端与第一个客户端完成连接并正常通信的时候,第二个客户端发起连接后,虽然可以正常连接,但是好像信息没有发生到服务端。
- 查看客户端2
ps aux | grep ./client
pstack [pid]
- 查看服务端
ps aux | grep ./server
pstack [pid]
sudo netstat -anp | grep 19999 //查看端口连接状态
我们可以看到,虽然服务端与两个客户端都建立了连接,但是单进程的服务端好像无法完成和两个客户端的通信,服务端只能和一个客户端建立稳定安全的通信,其他客户端就会陷入接收阻塞中。
那么可以有的解决方法就是,创建多个进程,让每一个进程都当做一个套接字,单独为客户端服务,有两种方式
- 父子进程:父进程执行监听的逻辑,子进程执行通信功能
- 多线程:主线程进行监听的逻辑,其他线程进行通信的功能
进程版本
任务分配
- 父进程:接收新的连接,完成
listen()
任务;创建子进程,这时子进程中的信息和父进程的信息是相同的。 - 子进程:和客户端进行TCP通信
再接收新的连接之后,不能让父进程带着新创建的套接字和客户端进行通信。
- 因为文件描述符只有1024个位置,如果让父进程进行通信的话,那么文件描述符被用完之后就又只能陷入阻塞的状态了。
所以说父进程还需要关闭套接字,防止文件描述符被占满。(不考虑高并发的情况)
修改后的服务端程序
#include "tcpser.hpp"
#include <signal.h>
#include <sys/wait.h>
//信号触发的回调函数
void sigcallback(int signo)
{
(void)signo;
while(1)
{
//-1 等待任何子进程退出,没有限制,waitpid 和 pid的作用一样
//NULL 表示父进程不关心子进程的终止状态
//WHONANG 表示子进程如果没有改变,就不阻塞,直接返回0
waitpid(-1,NULL,WNOHANG);
}
}
int main(int argc,char* argv[])
{
if(argc != 3)
{
cout<<"请输入正确的参数 [./ser] [ip] [port]"<<endl;
return 0;
}
signal(SIGCHLD,sigcallback);
string ip = argv[1];
uint16_t port = atoi(argv[2]);
TcpClass ts;
//创建套接字
if(!ts.CreateSocket())
{
return 0;
}
//绑定地址信息
if(!ts.Bind(ip,port))
{
return 0;
}
//监听
if(!ts.Listen(10))
{
return 0;
}
while(1)
{
TcpClass new_ts;
struct sockaddr_in new_addr;//将新建的套接字存放的结构体
//等待连接
if(!ts.Accept(new_ts,&new_addr))
{
continue;
}
//成功建立连接
cout<<"有一个新的连接 ["<<inet_ntoa(new_addr.sin_addr)<<"] ["<<ntohs(new_addr.sin_port)<<"] "<<endl;
//创建子进程,让子进程进行通信
int pid = fork();
if(pid < 0)
{
perror("fork");
exit(1);
}
else if(pid == 0)
{
//child
while(1)
{
//接收客户端的信息
string buf;
new_ts.Recv(buf);
cout<<"客户端说 : "<<buf<<endl;
cout<<"给客户端回话 : ";
fflush(stdout);
cin>>buf;
new_ts.Send(buf);
}
new_ts.Close();
exit(1);//退出当前进程,
}
else
{
//father
//删除父进程的文件描述符
new_ts.Close();
}
}
return 0;
}
- 运行结果
多线程版本
- 进程当中多个线程共享着同一个文件描述符,多线程了解
任务分配
- 主线程:负责监听
listen()
,并创造工作线程,给工作线程分配通信的任务 - 工作线程:和客户端进行通信功能
程序实现
#include "tcpser.hpp"
#include <pthread.h>
void* ThreadStart(void* arg)
{
pthread_detach(pthread_self());
tcpClass* ts = (tcpClass*)arg;
while(1)
{
string buf;
ts->Recv(buf);
cout<<"客户端发来请求,说 :"<<buf<<endl;
cout<<"给客户端回话 : ";
fflush(stdout);
cin>>buf;
ts->Send(buf);
}
ts->Close();
delete ts;
return NULL;
}
int main(int argc,char* argv[])
{
if(argc != 3)
{
cout<<"请输入正确的参数 [./ser] [ip] [port]"<<endl;
return 0;
}
string ip = argv[1];
uint16_t port = atoi(argv[2]);
tcpClass ts;
//创建套接字
if(!ts.CreateSocket())
{
return 0;
}
//绑定地址信息
if(!ts.Bind(ip,port))
{
return 0;
}
//建立监听
if(!ts.Listen(10))
{
return 0;
}
while(1)
{
tcpClass* new_ts = new tcpClass();
struct sockaddr_in peeraddr;
if(!ts.Accept(new_ts,peeraddr))
{
continue;
}
cout<<"有一个新的连接 "<<inet_ntoa(peeraddr.sin_addr)<<' '<<ntohs(peeraddr.sin_port)<<endl;
pthread_t tid;
int ret = pthread_create(&tid,NULL,ThreadStart,(void*)new_ts);
if(ret < 0)
{
perror("pthread_create");
return 0;
}
}
return 0;
}