目录
之前的代码,先关服务端,再次打开会出现错误bind:Address already in use

使用setsockopt 地址快速重用可解决(后续会讲套接字设置)
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
-
- #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
\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);
- }
- else
- close(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;
- else
- printf("buf = %s\n", buf);
- }
- close(newfd);
- }
原因:
虽然程序关闭,但是系统认为服务还在,所以会出现这种情况。
复习fork函数,wait阻塞,会使得子进程结束,父进程才结束,这样两个printf都会打印。
重点要fork()之后的代码,都会执行两遍,一遍是子进程,一遍是父进程。
- #include
- #include
- #include
-
- 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
- #include
- #include
- #include
- #include
- #include
- #include
- #include
-
- #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
\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等待
- }
- else
- close(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;
- else
- printf("buf = %s\n", buf);
- }
- close(newfd);
- }
实验效果:

现象:如果客户端退出,会产生僵尸进程

解决方法:使用信号的方式解决僵尸进程,注意flags设置为SA_RESTART的意义
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
-
- #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 0
- struct sigaction act;
- act.sa_handler = SigHandle;
- act.sa_flags = SA_RESTART; //如果flag = 0会退出,那么让被终止的进程继续运行。注意实验
- sigemptyset(&act.sa_mask);
- sigaction(SIGCHLD, &act, NULL);
- #else
- signal(SIGCHLD, SigHandle);
- #endif
-
- pid_t pid;
-
- if(argc < 3){
- fprintf(stderr, "%s
\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);
- }
- else
- close(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;
- else
- printf("buf = %s\n", buf);
- }
- close(newfd);
- }
目的:多线程占用的资源会更少
复习:
pthread_detach() 函数用于将指定的线程设置为分离模式。分离模式的线程在退出时会自动释放资源,不需要通过 pthread_join() 来等待线程结束并获取返回值。
函数原型为:
int pthread_detach(pthread_t thread);
参数 thread 是要设置为分离模式的线程标识符。
返回值:
注意事项:
pthread_detach() 函数,否则行为未定义。pthread_join() 来等待线程结束。pthread_detach() 或 pthread_attr_setdetachstate() 函数将其设置为分离模式。示例用法:
- #include
-
- 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
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
-
- #define BACKLOG 5
-
- void *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
\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;
- else
- printf("buf = %s\n", buf);
- }
- printf("client exited\n");
- close(newfd);
- return NULL;
- }
makefile也需要修改
-
- CC=gcc
- CFLAGS=-Wall
- all:client server
-
- server:server.c
- $(CC) $^ -Wall -o $@ -lpthread
-
- clean:
- rm client server
使用多线程实现TCP并发代码,并使用Makefile进行编译。提交代码和完成通信的截图
tcp_server.c
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
-
- #define CLIENT_MAX_NUM 5
-
- void * 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
\n" ,argv[0]); - return 0;
- }
-
- //1 创建socket
- sockfd = 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 绑定bind
- if(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
- #include
- #include
- #include
- #include
- #include
- #include
-
- #define CLIENT_MAX_NUM 5
-
- int main(int argc, char * argv[])
- {
- int clientfd;
- struct sockaddr_in server_addr;
- char buf[BUFSIZ];
-
- if( argc < 3)
- {
- printf("%s
\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 -lpthread
-
- clean:
- rm tcp_server tcp_client