**
先给自己打个广告,本人的微信公众号正式上线了,搜索:张笑生的地盘,主要关注嵌入式软件开发,股票基金定投,足球等等,希望大家多多关注,有问题可以直接留言给我,一定尽心尽力回答大家的问题。想要获取完整源码的,关注公众号后回复“socket2”即可。
一 why
**
一般地,我们会遇到这样一个场景,一个server需要同时并发地处理多个client的连接以及数据交互。
**
二 what
**
一个server对应多个client的连接场景如下:
实现原理
对于父进程server.c来说
- 父进程server.c用来监听每一个client,只实现accept的动作
- 每次server.c父进程检测到一个新的client时,就会创建一个新的server子进程,用来和对应的client实现通信(数据交互)
- 当对应的client退出时,对应的server子进程也需要退出(读不到数据),否则会产生僵尸进程
close(cfd); exit();
- 父进程server.c检测到子进程退出后,需要回收子进程
信号 — SIGCHLD()
waitpid();
- 父进程并不需要cfd,所以在父进程中需要关闭cfd, close(cfd);
对于server子进程来说
对每个server子进程:
- 子进程只需要cfd,并不需要lfd,所以每次进入子进程之后,需要关闭lfd, close(lfd);
-
**
三 how
**
server.c程序示例,想要获取完整源码的,关注公众号后回复“socket2”即可
void wait_child(int signo)
{
while (waitpid(0, NULL, WNOHANG) > 0);
return;
}
int main(int argc, char *argv[])
{
......
//创建socket
lfd = socket(AF_LOCAL, SOCK_STREAM, 0);
if (lfd == -1) {
perror("socket error");
return -1;
}
//如果套接字文件存在,删除套接字文件
unlink("server.sock");
//初始化server信息
serv.sun_family = AF_LOCAL;
strcpy(serv.sun_path, "server.sock");
//绑定
ret = bind(lfd, (struct sockaddr *)&serv, sizeof(serv));
if (ret == -1) {
perror("bind error");
return -1;
}
//设置监听,设置能够同时和服务端连接的客户端数量
ret = listen(lfd, 36);
if (ret == -1) {
perror("listen error");
return -1;
}
while (1) {
//等待客户端连接
cfd = accept(lfd, (struct sockaddr *)&client, &len);
if (cfd == -1) {
perror("accept error");
return -1;
}
printf("=====client bind file:%s\n", client.sun_path);
pid = fork(); //每次连接到一个新的客户端就创建一个子进程
/* 创建子进程同于通信,但是由于这个while循环父子进程都会进来,所以针对子进程,我们需要做退出处理
*/
......
}
if (pid == 0) {
while (1) {
recvlen = recv(cfd, buf, sizeof(buf), 0);
......
}
}
......
}
客户端client代码示例
int main(int argc, char *argv[])
{
......
//创建socket
lfd = socket(AF_LOCAL, SOCK_STREAM, 0);
if (lfd == -1) {
perror("socket error");
return -1;
}
//如果套接字文件存在,删除套接字文件
unlink("client.sock");
//给客户端绑定一个套接字文件
client.sun_family = AF_LOCAL;
strcpy(client.sun_path, "client.sock");
ret = bind(lfd, (struct sockaddr *)&client, sizeof(client));
if (ret == -1) {
perror("bind error");
return -1;
}
//初始化server信息
serv.sun_family = AF_LOCAL;
strcpy(serv.sun_path, "server.sock");
//连接
connect(lfd, (struct sockaddr *)&serv, sizeof(serv));
while (1) {
fgets(buf, sizeof(buf), stdin);
send(lfd, buf, strlen(buf)+1, 0);
recv(lfd, buf, sizeof(buf), 0);
printf("recv buf %s\n", buf);
}
......
}
上面的server使用的是创建多进程的方式来实现的,当然我们也可以采用多线程方式来实现,每次连接到一个客户端时,创建一个线程,用来实现和client的数据交互
server.c程序代码
struct s_info { //定义一个结构体,将地址结构和cfd捆绑
struct sockaddr_un cliaddr;
int connfd;
};
void *do_work(void *arg)
{
int n, i;
struct s_info *ts = (struct s_info *)arg;
char buf[1024] = {0};
int recvlen;
while (1) {
recvlen = recv(ts->connfd, buf, sizeof(buf), 0);
if (recvlen < 0) {
perror("recv error");
exit(1);
} else if (recvlen == 0) {
printf("client %d close...\n", ts->connfd);
close(ts->connfd);
return 0;
} else {
printf("recv buf %s\n", buf);
send(ts->connfd, buf, recvlen, 0);
}
}
close(ts->connfd);
return NULL;
}
int main(int argc, char *argv[])
{
int lfd ,ret, cfd;
struct sockaddr_un serv, client;
socklen_t len = sizeof(client);
int i;
pthread_t tid;
struct s_info ts[256]; //根据最大线程数创建结构体数组
//创建socket
lfd = socket(AF_LOCAL, SOCK_STREAM, 0);
if (lfd == -1) {
perror("socket error");
return -1;
}
//如果套接字文件存在,删除套接字文件
unlink("server.sock");
//初始化server信息
serv.sun_family = AF_LOCAL;
strcpy(serv.sun_path, "server.sock");
//绑定
ret = bind(lfd, (struct sockaddr *)&serv, sizeof(serv));
if (ret == -1) {
perror("bind error");
return -1;
}
//设置监听,设置能够同时和服务端连接的客户端数量
ret = listen(lfd, 36);
if (ret == -1) {
perror("listen error");
return -1;
}
while (1) {
//等待客户端连接
cfd = accept(lfd, (struct sockaddr *)&client, &len);
if (cfd == -1) {
perror("accept error");
return -1;
}
printf("=====client bind file:%s\n", client.sun_path);
/* 创建子线程用于通信
*/
}
close(lfd);
return 0;
}