Tcp面向链接、面向字节流和文件的读写非常类似():客户端创建套接字主动建立连接,服务器监听套接字一直等待连接的到来,监听到一个,就创建一个新的套接字用于IO
服务器:
创建套接字:
填充网络信息并绑定
把套接字设置为监听状态——TCP特有
根据监听套接字,获取客户端的链接:——TCP特有
服务器收发消息:
客户端:
启动时:要传入目标服务器的ip port
创建套接字:
也是不需要手动绑定客户端socket,在于服务器连接完成后自己会绑定网络信息
虽然客户端自己不用绑定网络信息,但是客户端首先要给服务器发消息,所以也是要为目标服务器填一下server的网络类型结构体,供发送使用,
和目标服务器建立连接:——TCP特有
向建立连接成功的套接字中读写数据就完成了数据的发送和接收:
优化:服务器支持多客户端访问——多进程或者多线程
v2:多进程:在获取连接成功后,创建子进程去执行此次链接成功的客户端的求情;
出现的问题:创建子进程文件描述符表会发生拷贝
父进程的文件描述符表就会随着获取连接成功的次数增加,这样就会造成空间的消耗,所以创建完成之后,父子进程应该关闭自己不需要的文件描述符,(父进程关闭新建文件描述符,子进程关闭监听文件描述符)
但是即使这样还是有个问题:子进程的退出需要父进程等待,如果父进程等待就是阻塞式的等待,那么任然不能实现多客户端连接,
解决方案:子进程再创建一个子进程(孙子进程),然后让子进程退出,孙子进程去完成收发;
v3:多进程信号版
signal(SIGCHLD,SIG_IGN);//对SIG_IGN信号进行忽略,子进程退出后不用等待自动释放
就不用创建孙子进程,因为父进程不用等待了,就直接用子进程执行收发
v4:多线程
主线程和新线程不需要关闭所谓的文件描述符,因为他们是共享文件描述符表的如果一个关闭,另外一个也就看不到了
问题:主线程也是会阻塞等待:解决:子线程和主主线程分离
// pthread_detach(pthread_self());//把自己分离
但是子线程要执行的函数传参的时候如果只传socket的话,因为主线程会一直去监听创建新的socket,所以socket的值是一直变化的不能直接传给子线程执行,要在建立子线程之前先把他保存起来:
//创建一个类用来保存可靠的保存文件描述符,因为如果是创建多线程来收发消息的话,就要把获取到的文件描述符值传过去,但是主线程又会不停地获取,那么如果直接传&sockfd的话,在主线程再次去获取新的是,sockfd这个变量里面保存的值就会被清空(线程调度)
// class ThreadData
// {
// public:
// ThreadData(int sock,TcpServer*ptr,sockaddr_in peer):sockfd(sock),svr_ptr(ptr),addr(peer)
// {}
// int Sockfd()
// {
// return sockfd;
// }
// TcpServer*GetServer() {return svr_ptr;};
// ~ThreadData()
// {}
// private:
// int sockfd;
// TcpServer* svr_ptr;
// public:
// InetAddr addr;
// };
// static void *HandlerRequest(void*args)
// {
// ThreadData *td=static_cast<ThreadData*>(args);
// //主线程和新线程不需要关闭所谓的文件描述符,因为他们是共享文件描述符表的如果一个关闭,另外一个也就看不到了
// pthread_detach(pthread_self());//把自己分离
// //现在就可以执行收发喽
// td->GetServer()->Service(td->Sockfd(),td->addr);
// close(td->Sockfd());
// delete td;
// return nullptr;
// }
// //v4多线程
// //把这一次的文件描述符保存起来
// ThreadData *td=new ThreadData(sockfd,this,peer);
// pthread_t tid;
// pthread_create(&tid,nullptr,HandlerRequest,td);
总代码链接