上篇文章简单的介绍了一下Linux下套接字通信的相关知识:
http://blog.csdn.net/qq_29503203/article/details/60961537
但是存在一个缺陷就是只能进行单进程通信,我们都知道实际上不可能一个服务器一次只能有一个客户端,所以在这里对其进行一个改进。
多进程套接字TCP通信
我们通过fork出子进程去完成客户端发来的请求,而父进程只需用去accpet连接请求。这里还需注意的是:既然创建出子进程,那么就得考虑它的回收。先前学习回收子进程的方法很多,比如:通过发信号,通过函数(一般是父进程对子进程的一个等待)
今天将介绍一种新的方式,即在子进程中继续创建子进程,然后通过init系统对其进行回收,下来看着代码继续详说
server.c
#include
#include
#include
#include
#include
#include
#include
#include
int startup(int _port,const char* _ip)
{
int sock=socket(AF_INET,SOCK_STREAM,0);
if(sock<0)
{
perror("socket");
exit(1);
}
int opt=1;
if(setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt))<0)
{
perror("setsockopt");
exit(2);
}
struct sockaddr_in local;
local.sin_family=AF_INET;
local.sin_port=htons(_port);
local.sin_addr.s_addr=inet_addr(_ip);
socklen_t len=sizeof(local);
if(bind(sock,(struct sockaddr*)&local,len)<0)
{
perror("bind");
exit(2);
}
if(listen(sock,5)<0)
{
perror("listen");
exit(3);
}
return sock;
}
int main(int argc,char* argv[])
{
if(argc!=3)
{
printf("Usage: [local_ip] [local_port]",argv[0]);
return 3;
}
int listen_socket=startup(atoi(argv[2]),argv[1]);
struct sockaddr_in remote;
socklen_t len=sizeof(struct sockaddr_in);
while(1)
{
int socket=accept(listen_socket,(struct sockaddr*)&remote,&len);
if(socket<0)
{
perror("accept");
continue;
}
pid_t pid=fork();
if(pid==0)
{
if(fork()>0)
{
exit(1);
}
char buf[1024];
while(1)
{
ssize_t _s=read(socket,buf,sizeof(buf)-1);
if(_s>0)
{
buf[_s]=0;
printf("client# %s\n",buf);
}
else
{
printf("client is quit!\n");
break;
}
printf("client,ip:%s,port:%d\n",inet_ntoa(remote.sin_addr)\
,ntohs(remote.sin_port));
}
}
else if(pid>0)
{
close(socket);
waitpid(-1,NULL,WNOHANG);
}
else
{
perror("fork");
exit(2);
}
}
return 0;
}
这份代码修改了两个地方:
(1)在socket创建成功后,使用了setsockopt函数,下面将从两方面对其进行一个解释:
- 它是用来解决什么问题的?
- 不这样做会产生怎样的后果?
- 函数原型的说明。
1>之前在测试的时候,当我们启动server,然后启动client,再用Ctrl+C使server终止,这时马上再运行server,结果是:
bind error:Address already in use 这时因为虽然server的应用程序终止了,但TCP协议的连接并没有完全断开,回忆一下TCP四次挥手的过程,因此不能再监听同样的server端口。
在server的TCP没有连接没有完全断开之前不允许重新监听是不合理的,TCP没有完全断开是指connfd(127.0.0.1:8080)没有完全断开,而我们重新监听的是listenfd(0.0.0.0:8080),虽然占用同一个端口,但IP地址不同,connfd对应的是与某个客户端通讯的一个具体的IP地址,而Listenfd对应的是wildcard address(通配符地址)。
解决这一办法就是使用setsockopt函数,设置socket描述符的选项SO_REUSEADDR为1,表示允许创建端口号相同但IP地址不同的多个socket描述符,通常在socket()和bind()调用之间调用该函数。
2>想想一个场景,当server服务器异常退出时,这会儿它处于TIME_WAIT状态(等待2msl长的时间),主动关闭的一方处于TIME_WAIT状态,而ip和端口号依然被占用,此时客户端无法连接到服务器,而服务器又无法重新启动,会造成网络数据的流失异常等多种情况,解决办法就是采用setsockopt函数。
3>函数原型,所在头文件及其返回值
(2)回收子进程
这里是这样一个情况,原本fork()出一个子进程,用它来执行回写等操作,而父进程里去等待子进程以致回收,但是考虑到性能问题,时间资源消耗问题,我们采取在这个子进程里再fork一次,如果子进程又创建成功,那么我们就可以使类似于它的父进程的这个说法的进程(简单来说就是刚开始父进程创建出来的那个进程)退出,然后该子进程就成为了孤儿进程,我们又知道孤儿进程一般是通过1号进程init系统回收的,所以这样将节约不少时间(不让儿子进程等待孙子进程太久而消耗太多的系统资源)。如下图分析:
多线程套接字TCP通信
在前边的系统编程的学习中,我们知道线程是进程内部的一个执行流,由于同一进程的多个线程共享同一地址空间,如果可以把进程做的事交给多个线程去做就会节约不少资源,而我们又知道进程是程序的一次动态的执行过程,系统中的进程数过于多的话,会增加系统的负担,所以这里采用多线程实现通信对上面两种进行优化。
实现方法:
(1)主线程中创建出一个新线程,新线程的执行函数是读取信息,类似于上边的多进程间的通信。
(2)这里需要注意的就是线程等待和回收的问题。我们知道默认情况下,线程被创建成可结合的,如果我们通过pthread_join()的话,这里主线程阻塞式的去等待新线程会耗费很多的时间,所以这里我们可以将新的线程进行分离,分离之后的线程就不需要主线程去等待,而是由操作系统区回收。
代码实现:
tserver.c
#include
#include
#include
#include
#include
#include
#include
#include
int startup(int _port,char* _ip)
{
assert(_ip);
int sock=socket(AF_INET,SOCK_STREAM,0);
if(sock<0)
{
perror("socket");
exit(2);
}
struct sockaddr_in local;
local.sin_family=AF_INET;
local.sin_port=htons(_port);
local.sin_addr.s_addr=inet_addr(_ip);
socklen_t len=sizeof(local);
int opt = 1;
int stat= setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
if(bind(sock,(struct sockaddr*)&local,len)<0)
{
perror("bind");
exit(3);
}
if(listen(sock,5)<0)
{
perror("listen");
exit(4);
}
return sock;
}
void* handler_fd(void* arg)
{
int sock=*((int*)arg);
pthread_detach(pthread_self());
char buf[1024];
while(1)
{
ssize_t _s=read(sock,buf,sizeof(buf)-1);
if(_s>0)
{
buf[_s-1]=0;
printf("Client# %s\n",buf);
}
else
{
printf("Client quit!");
close(sock);
break;
}
}
int main(int argc,char*argv[])
{
if(argc!=3)
{
printf("Usage:%s,[local_ip] [local_port]\n",argv[0]);
exit(1);
}
int listen_sock=startup(atoi(argv[2]),argv[1]);
struct sockaddr_in peer;
socklen_t len=sizeof(peer);
while(1)
{
int sock=accept(listen_sock,(struct sockaddr*)&peer,&len);
if(sock<0)
{
perror("accept");
continue;
}
pthread_t tid;
int ret=pthread_create(&tid,NULL,handler_fd,&sock);
if(ret<0)
{
printf("pthread create failed!\n");
exit(5);
}
pthread_detach(tid);
}
return 0;
}
client.c不用改变,有关进程,线程套接字的通信就介绍到此。