目录
[1 TCP 多进程并发](#1 TCP 多进程并发)
[1.1 现象:](#1.1 现象:)
[1.2 多进程并发](#1.2 多进程并发)
[2 僵尸进程处理](#2 僵尸进程处理)
[3 TCP并发多线程](#3 TCP并发多线程)
[4 练习](#4 练习)
1 TCP 多进程并发
1.1 现象:
之前的代码,先关服务端,再次打开会出现错误bind:Address already in use
使用setsockopt 地址快速重用可解决(后续会讲套接字设置)
cpp
#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);
}
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);
}
原因:
虽然程序关闭,但是系统认为服务还在,所以会出现这种情况。
1.2 多进程并发
复习fork函数,wait阻塞,会使得子进程结束,父进程才结束,这样两个printf都会打印。
重点要fork()之后的代码,都会执行两遍,一遍是子进程,一遍是父进程。
cpp
#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中两个原来参数,使用函数进行转换
cpp
char * inet_ntoa(struct in_addr in);
cpp
#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等待
}
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);
}
实验效果:
2 僵尸进程处理
现象:如果客户端退出,会产生僵尸进程
解决方法:使用信号的方式解决僵尸进程,注意flags设置为SA_RESTART的意义
cpp
#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 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<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);
}
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);
}
3 TCP并发多线程
目的:多线程占用的资源会更少
复习:
pthread_detach()
函数用于将指定的线程设置为分离模式。分离模式的线程在退出时会自动释放资源,不需要通过 pthread_join()
来等待线程结束并获取返回值。
函数原型为:
cpp
int pthread_detach(pthread_t thread);
参数 thread
是要设置为分离模式的线程标识符。
返回值:
- 成功时,返回 0。
- 失败时,返回一个非零的错误码。
注意事项:
- 必须在线程执行之前或者在其它线程中调用
pthread_detach()
函数,否则行为未定义。 - 一旦线程被设置为分离模式,就无法再使用
pthread_join()
来等待线程结束。 - 分离模式的线程会在退出时自动释放其资源,但必须确保线程在退出前不会产生资源泄漏。
- 默认情况下,线程是非分离模式,需要显式调用
pthread_detach()
或pthread_attr_setdetachstate()
函数将其设置为分离模式。
示例用法:
cpp
#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;
}
cpp
#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 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<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;
else
printf("buf = %s\n", buf);
}
printf("client exited\n");
close(newfd);
return NULL;
}
makefile也需要修改
cpp
CC=gcc
CFLAGS=-Wall
all:client server
server:server.c
$(CC) $^ -Wall -o $@ -lpthread
clean:
rm client server
4 练习
使用多线程实现TCP并发代码,并使用Makefile进行编译。提交代码和完成通信的截图
tcp_server.c
cpp
#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 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 <ip> <port>\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
cpp
#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 5
int 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
cpp
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