目录
1 TCP 多进程并发
1.1 现象:
1.2 多进程并发
2 僵尸进程处理
3 TCP并发多线程
4 练习
1 TCP 多进程并发
1.1 现象:
之前的代码,先关服务端,再次打开会出现错误bind:Address already in use
使用setsockopt 地址快速重用可解决(后续会讲套接字设置)
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>#define BACKLOG 5
void ClinetHandle(int newfd);
int main(int argc, char *argv[])
{int fd, newfd;struct sockaddr_in addr, clint_addr;socklen_t addrlen = sizeof(clint_addr);pid_t pid;if(argc < 3){fprintf(stderr, "%s<addr><port>\n", argv[0]);exit(0);}/*创建套接字*/fd = socket(AF_INET, SOCK_STREAM, 0);if(fd < 0){perror("socket");exit(0);}addr.sin_family = AF_INET;addr.sin_port = htons( atoi(argv[2]) );if ( inet_aton(argv[1], &addr.sin_addr) == 0) {fprintf(stderr, "Invalid address\n");exit(EXIT_FAILURE);}/*地址快速重用*/int flag=1,len= sizeof (int); if ( setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &flag, len) == -1) { perror("setsockopt"); exit(1); } /*绑定通信结构体*/if(bind(fd, (struct sockaddr *)&addr, sizeof(addr) ) == -1){perror("bind");exit(0);}/*设置套接字为监听模式*/if(listen(fd, BACKLOG) == -1){perror("listen");exit(0);}while(1){/*接受客户端的连接请求,生成新的用于和客户端通信的套接字*/newfd = accept(fd, (struct sockaddr *)&clint_addr, &addrlen);if(newfd < 0){perror("accept");exit(0);}printf("addr:%s port:%d\n", inet_ntoa(clint_addr.sin_addr), ntohs(clint_addr.sin_port) );if( (pid = fork() ) < 0){perror("fork");exit(0);}else if(pid == 0){close(fd);ClinetHandle(newfd);exit(0);}elseclose(newfd);}close(fd);return 0;
}
void ClinetHandle(int newfd){int ret;char buf[BUFSIZ] = {};while(1){//memset(buf, 0, BUFSIZ);bzero(buf, BUFSIZ);ret = read(newfd, buf, BUFSIZ);if(ret < 0){perror("read");exit(0);}else if(ret == 0)break;elseprintf("buf = %s\n", buf);}close(newfd);
}
原因:
虽然程序关闭,但是系统认为服务还在,所以会出现这种情况。
1.2 多进程并发
复习fork函数,wait阻塞,会使得子进程结束,父进程才结束,这样两个printf都会打印。
重点要fork()之后的代码,都会执行两遍,一遍是子进程,一遍是父进程。
#include <stdio.h>
#include <wait.h>
#include <stdlib.h>int main(int argc, char *argv[])
{pid_t pid = fork();if(pid < 0){perror("fork");exit(0);}else if(pid == 0){printf("This is child process.\n");}else{printf("This is father process.\n");wait(NULL);}return 0;
}
多进程并发服务端实现:
注意子进程和父进程中的处理细节,防止子进程产生孙进程,防止父、子进程未关闭占用的资源。
另外启用了accept中两个原来参数,使用函数进行转换
char * inet_ntoa(struct in_addr in);
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>#define BACKLOG 5
void ClinetHandle(int newfd);
int main(int argc, char *argv[])
{int fd, newfd;struct sockaddr_in addr, clint_addr;socklen_t addrlen = sizeof(clint_addr);pid_t pid;if(argc < 3){fprintf(stderr, "%s<addr><port>\n", argv[0]);exit(0);}/*创建套接字*/fd = socket(AF_INET, SOCK_STREAM, 0);if(fd < 0){perror("socket");exit(0);}addr.sin_family = AF_INET;addr.sin_port = htons( atoi(argv[2]) );if ( inet_aton(argv[1], &addr.sin_addr) == 0) {fprintf(stderr, "Invalid address\n");exit(EXIT_FAILURE);}/*地址快速重用*/int flag=1,len= sizeof (int); if ( setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &flag, len) == -1) { perror("setsockopt"); exit(1); } /*绑定通信结构体*/if(bind(fd, (struct sockaddr *)&addr, sizeof(addr) ) == -1){perror("bind");exit(0);}/*设置套接字为监听模式*/if(listen(fd, BACKLOG) == -1){perror("listen");exit(0);}while(1){/*接受客户端的连接请求,生成新的用于和客户端通信的套接字*/newfd = accept(fd, (struct sockaddr *)&clint_addr, &addrlen);if(newfd < 0){perror("accept");exit(0);}//注意理解转换函数printf("addr:%s port:%d\n", inet_ntoa(clint_addr.sin_addr), ntohs(clint_addr.sin_port) );if( (pid = fork() ) < 0){perror("fork");exit(0);}else if(pid == 0){close(fd); //子进程需要关闭fd,对子进程来讲已经不适用fd了,占用了资源ClinetHandle(newfd);exit(0); //退出子进程,防止后面生成孙进程,也进入了accept等待}elseclose(newfd); //父进程关闭newfd,因为newfd被子进程占用了}close(fd);return 0;
}void ClinetHandle(int newfd){int ret;char buf[BUFSIZ] = {};while(1){//memset(buf, 0, BUFSIZ);bzero(buf, BUFSIZ);ret = read(newfd, buf, BUFSIZ);if(ret < 0){perror("read");exit(0);}else if(ret == 0)break; elseprintf("buf = %s\n", buf);}close(newfd);
}
实验效果:
2 僵尸进程处理
现象:如果客户端退出,会产生僵尸进程
解决方法:使用信号的方式解决僵尸进程,注意flags设置为SA_RESTART的意义
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <signal.h>
#include <sys/wait.h>#define BACKLOG 5
void SigHandle(int sig){if(sig == SIGCHLD){printf("client exited\n");wait(NULL);}
}
void ClinetHandle(int newfd);
int main(int argc, char *argv[])
{int fd, newfd;struct sockaddr_in addr, clint_addr;socklen_t addrlen = sizeof(clint_addr);#if 0struct sigaction act;act.sa_handler = SigHandle;act.sa_flags = SA_RESTART; //如果flag = 0会退出,那么让被终止的进程继续运行。注意实验sigemptyset(&act.sa_mask);sigaction(SIGCHLD, &act, NULL);
#elsesignal(SIGCHLD, SigHandle);
#endifpid_t pid;if(argc < 3){fprintf(stderr, "%s<addr><port>\n", argv[0]);exit(0);}/*创建套接字*/fd = socket(AF_INET, SOCK_STREAM, 0);if(fd < 0){perror("socket");exit(0);}addr.sin_family = AF_INET;addr.sin_port = htons( atoi(argv[2]) );if ( inet_aton(argv[1], &addr.sin_addr) == 0) {fprintf(stderr, "Invalid address\n");exit(EXIT_FAILURE);}/*地址快速重用*/int flag=1,len= sizeof (int); if ( setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &flag, len) == -1) { perror("setsockopt"); exit(1); } /*绑定通信结构体*/if(bind(fd, (struct sockaddr *)&addr, sizeof(addr) ) == -1){perror("bind");exit(0);}/*设置套接字为监听模式*/if(listen(fd, BACKLOG) == -1){perror("listen");exit(0);}while(1){/*接受客户端的连接请求,生成新的用于和客户端通信的套接字*/newfd = accept(fd, (struct sockaddr *)&clint_addr, &addrlen);if(newfd < 0){perror("accept");exit(0);}printf("addr:%s port:%d\n", inet_ntoa(clint_addr.sin_addr), ntohs(clint_addr.sin_port) );if( (pid = fork() ) < 0){perror("fork");exit(0);}else if(pid == 0){close(fd);ClinetHandle(newfd);exit(0);}elseclose(newfd);}close(fd);return 0;
}
void ClinetHandle(int newfd){int ret;char buf[BUFSIZ] = {};while(1){//memset(buf, 0, BUFSIZ);bzero(buf, BUFSIZ);ret = read(newfd, buf, BUFSIZ);if(ret < 0){perror("read");exit(0);}else if(ret == 0)break;elseprintf("buf = %s\n", buf);}close(newfd);
}
3 TCP并发多线程
目的:多线程占用的资源会更少
复习:
pthread_detach()
函数用于将指定的线程设置为分离模式。分离模式的线程在退出时会自动释放资源,不需要通过 pthread_join()
来等待线程结束并获取返回值。
函数原型为:
int pthread_detach(pthread_t thread);
参数 thread
是要设置为分离模式的线程标识符。
返回值:
- 成功时,返回 0。
- 失败时,返回一个非零的错误码。
注意事项:
- 必须在线程执行之前或者在其它线程中调用
pthread_detach()
函数,否则行为未定义。 - 一旦线程被设置为分离模式,就无法再使用
pthread_join()
来等待线程结束。 - 分离模式的线程会在退出时自动释放其资源,但必须确保线程在退出前不会产生资源泄漏。
- 默认情况下,线程是非分离模式,需要显式调用
pthread_detach()
或pthread_attr_setdetachstate()
函数将其设置为分离模式。
示例用法:
#include <pthread.h>void* thread_func(void* arg) {// 线程执行的代码return NULL;
}int main() {pthread_t tid;// 创建线程if (pthread_create(&tid, NULL, thread_func, NULL) != 0) {// 处理创建线程失败的情况return -1;}// 设置线程为分离模式if (pthread_detach(tid) != 0) {// 处理设置线程分离模式失败的情况return -1;}// 继续执行其他操作// ...return 0;
}
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <pthread.h>#define BACKLOG 5void *ClinetHandle(void *arg);
int main(int argc, char *argv[])
{int fd, newfd;struct sockaddr_in addr, clint_addr;pthread_t tid;socklen_t addrlen = sizeof(clint_addr);if(argc < 3){fprintf(stderr, "%s<addr><port>\n", argv[0]);exit(0);}/*创建套接字*/fd = socket(AF_INET, SOCK_STREAM, 0);if(fd < 0){perror("socket");exit(0);}addr.sin_family = AF_INET;addr.sin_port = htons( atoi(argv[2]) );if ( inet_aton(argv[1], &addr.sin_addr) == 0) {fprintf(stderr, "Invalid address\n");exit(EXIT_FAILURE);}/*地址快速重用*/int flag=1,len= sizeof (int); if ( setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &flag, len) == -1) { perror("setsockopt"); exit(1); } /*绑定通信结构体*/if(bind(fd, (struct sockaddr *)&addr, sizeof(addr) ) == -1){perror("bind");exit(0);}/*设置套接字为监听模式*/if(listen(fd, BACKLOG) == -1){perror("listen");exit(0);}while(1){/*接受客户端的连接请求,生成新的用于和客户端通信的套接字*/newfd = accept(fd, (struct sockaddr *)&clint_addr, &addrlen);if(newfd < 0){perror("accept");exit(0);}printf("addr:%s port:%d\n", inet_ntoa(clint_addr.sin_addr), ntohs(clint_addr.sin_port) );pthread_create(&tid, NULL, ClinetHandle, &newfd);pthread_detach(tid); //把线程属性设置为分离模式}close(fd);return 0;
}
void *ClinetHandle(void *arg){int ret;char buf[BUFSIZ] = {};int newfd = *(int *)arg;while(1){//memset(buf, 0, BUFSIZ);bzero(buf, BUFSIZ);ret = read(newfd, buf, BUFSIZ);if(ret < 0){perror("read");exit(0);}else if(ret == 0)break;elseprintf("buf = %s\n", buf);}printf("client exited\n");close(newfd);return NULL;
}
makefile也需要修改
CC=gcc
CFLAGS=-Wall
all:client serverserver:server.c$(CC) $^ -Wall -o $@ -lpthreadclean:rm client server
4 练习
使用多线程实现TCP并发代码,并使用Makefile进行编译。提交代码和完成通信的截图
tcp_server.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>#define CLIENT_MAX_NUM 5void * ClientHandle(void *arg);int main(int argc, char * argv[])
{int sockfd, clientfd;struct sockaddr_in server_addr,client_addr;pthread_t tid;socklen_t addrlen = sizeof(client_addr);if( argc < 3){printf("%s <ip> <port>\n",argv[0]);return 0;}//1 创建socketsockfd = socket(AF_INET, SOCK_STREAM,0);if(sockfd == -1){perror("socket");return 0;}server_addr.sin_family = AF_INET;server_addr.sin_port = htons( atoi(argv[2]) ) ;if( inet_aton(argv[1], &server_addr.sin_addr) == 0){printf("Invalid address:%s\n",argv[1]);return 0;}/*地址快速重用*/int flag = 1, len = sizeof(int);if( setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &flag,len) == -1){perror("setsockopt");return 0;}//2 绑定bindif(bind(sockfd, (struct sockaddr *)&server_addr,sizeof(server_addr)) == -1){perror("bind");return 0;}//3 监听if(listen(sockfd, CLIENT_MAX_NUM) == -1){perror("listen");return 0;}while(1){//4 等待连接clientfd = accept(sockfd, (struct sockaddr *)&client_addr, &addrlen); if( clientfd == -1){perror("accept");return 0;}printf("addr:%s port:%d\n",inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));pthread_create(&tid, NULL, ClientHandle, &clientfd);pthread_detach(tid); //线程属性设置为分离模式}close(sockfd);return 0;
}void * ClientHandle(void *arg)
{int ret;char buf[BUFSIZ] = {};int clientfd = *(int *)arg;while(1){//bzero(buf, BUFSIZ);memset(buf, 0, BUFSIZ);ret = read(clientfd, buf, BUFSIZ);if(ret < 0){perror("read");exit(0);}else if( ret == 0 ){break;}else{printf("buf = %s\n", buf);} }printf("client exited\n");close(clientfd);return NULL;}
tcp_client.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>#define CLIENT_MAX_NUM 5int main(int argc, char * argv[])
{int clientfd;struct sockaddr_in server_addr;char buf[BUFSIZ];if( argc < 3){printf("%s <ip> <port>\n",argv[0]);return 0;}clientfd = socket(AF_INET, SOCK_STREAM,0);if(clientfd == -1){perror("socket");return 0;}server_addr.sin_family = AF_INET;server_addr.sin_port = htons( atoi(argv[2]) ) ;if( inet_aton(argv[1], &server_addr.sin_addr) == 0){printf("Invalid address:%s\n",argv[1]);return 0;}if(connect(clientfd, (struct sockaddr *)&server_addr,sizeof(server_addr)) == -1){perror("connect");return 0;}while(1){printf(">");fgets(buf, BUFSIZ, stdin);write(clientfd, buf, strlen(buf));}close(clientfd);return 0;
}
makefile
CC=gcc
CFLAGS=-Wall
all:tcp_client tcp_server
tcp_server:tcp_server.c$(CC) tcp_server.c -Wall -o tcp_server -lpthreadclean:rm tcp_server tcp_client