1. 多线程TCPsocket编程
1.1 服务端
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
struct ThreadInfo
{
int newsockfd_;
};
void* TcpThreadStart(void* arg)
{
pthread_detach(pthread_self());
struct ThreadInfo* ti = (struct ThreadInfo*)arg;
int newsockfd = ti->newsockfd_;
while(1)
{
//接收
char buf[1024] = {0};
ssize_t recv_size = recv(newsockfd, buf, sizeof(buf) - 1, 0);
if(recv_size < 0)
{
perror("recv");
continue;
}
else if(recv_size == 0)
{
printf("peer close connect\n");
close(newsockfd);
break;
}
printf("%s\n", buf);
memset(buf, '\0', sizeof(buf));
strcpy(buf, "i am server!!!");
send(newsockfd, buf, strlen(buf), 0);
}
delete ti;
return NULL;
}
int main()
{
int listen_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(listen_sock < 0)
{
perror("socket");
return 0;
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(28989);
//0.0.0.0 : 本地所有的网卡地址
addr.sin_addr.s_addr = inet_addr("0.0.0.0");//私网ip
int ret = bind(listen_sock, (struct sockaddr*)&addr, sizeof(addr));
if(ret < 0)
{
perror("bind");
return 0;
}
ret = listen(listen_sock, 1);
if(ret < 0)
{
perror("listen");
return 0;
}
while(1)
{
struct sockaddr_in cli_addr;
socklen_t cli_addrlen = sizeof(cli_addr);
int newsockfd = accept(listen_sock, (struct sockaddr*)&cli_addr, &cli_addrlen);
if(newsockfd < 0)
{
perror("accept");
return 0;
}
printf("accept new connect from client %s:%d\n", inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port));
struct ThreadInfo* ti = new ThreadInfo;
ti->newsockfd_ = newsockfd;
//创建线程
pthread_t tid;
ret = pthread_create(&tid, NULL, TcpThreadStart, (void*)ti);
if(ret < 0)
{
close(newsockfd);
delete ti;
continue;
}
}
close(listen_sock);
return 0;
}
1.2 客户端
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main()
{
int sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(sockfd < 0)
{
perror("socket");
return 0;
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(28989);
// 0.0.0.0 : 本地所有的网卡地址
addr.sin_addr.s_addr = inet_addr("82.157.94.99");//必须使用公网ip
int ret = connect(sockfd, (struct sockaddr*)&addr, sizeof(addr));
if(ret < 0)
{
perror("connect");
return 0;
}
while(1)
{
char buf[1024] = "i am client1111222";
send(sockfd, buf, strlen(buf), 0);
memset(buf, '\0', sizeof(buf));
//接收
ssize_t recv_size = recv(sockfd, buf, sizeof(buf) - 1, 0);
if(recv_size < 0)
{
perror("recv");
continue;
}
else if(recv_size == 0)
{
printf("peer close connect\n");
close(sockfd);
continue;
}
printf("%s\n", buf);
sleep(1);
}
close(sockfd);
return 0;
}
1.3 常见问题
(1)服务端的侦听端口,如果udp已经绑定了某一个端口,tcp是否可以再次绑定?
一个端口同时可以被udp程序和tcp程序所绑定,在网络层已经能够区分一个网络数据是传输给传输层的tcp协议还是udp协议
(2)telnet测试tcp端口是否开放(是否在监听),引申含义:服务端是否正常工作?
可以使用telnet模拟tcp的客户端,给tcp的服务端发送建立连接的请求,也可以模拟三次握手的过程,建立tcp连接
(3)关于backlog
记住:能够和服务端建立的连接数量 = backlog + 1
2. 多进程TCPsocket编程
想要让子进程与客户端进行通信,则子进程当中一定要存在新连接的套接字,否则子进程就没有办法和客户端进行通信
结论:一定是父进程接收新连接完毕之后,再fork子进程
2.1 服务端
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
void sigcallback(int signo)
{
printf("recv signo : %d\n", signo);
//当前的wait是进程等待的阻塞接口, 但是应用的场景一定是子进程退出之后,
//父进程收到了SIGCHLD信号之后, 才会回调sigcallback函数, 才会调用wait
wait(NULL);
}
int main()
{
signal(SIGCHLD, sigcallback);
int listen_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(listen_sock < 0)
{
perror("socket");
return 0;
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(28989);
addr.sin_addr.s_addr = inet_addr("0.0.0.0");
int ret = bind(listen_sock, (struct sockaddr*)&addr, sizeof(addr));
if(ret < 0)
{
perror("bind");
return 0;
}
ret = listen(listen_sock, 5);
if(ret < 0)
{
perror("listen");
return 0;
}
while(1)
{
int new_sock = accept(listen_sock, NULL, NULL);
if(new_sock < 0)
{
continue;
}
//创建子进程
int pid = fork();
if(pid < 0)
{
//创建子进程失败, 但是接受新连接成功
close(new_sock);
continue;
}
else if(pid == 0)
{
//child
close(listen_sock);
while(1)
{
//recv and send
char buf[1024] = {0};
ssize_t recv_size = recv(new_sock, buf, sizeof(buf) - 1, 0);
if(recv_size < 0)
{
perror("recv");
continue;
}
else if(recv_size == 0)
{
printf("peer shutdown!!\n");
close(new_sock);
//子进程退出掉 -- TODO
exit(1);
}
printf("client say: \"%s\"\n", buf);
memset(buf, '\0', sizeof(buf));
strcpy(buf, "i am server");
send(new_sock, buf, strlen(buf), 0);
}
}
else
{
//father
close(new_sock);
}
}
return 0;
}
2.2 客户端
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main()
{
int sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(sockfd < 0)
{
perror("socket");
return 0;
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(28989);
// 0.0.0.0 : 本地所有的网卡地址
addr.sin_addr.s_addr = inet_addr("82.157.94.99");//必须使用公网ip
int ret = connect(sockfd, (struct sockaddr*)&addr, sizeof(addr));
if(ret < 0)
{
perror("connect");
return 0;
}
while(1)
{
char buf[1024] = "i am client1111222";
send(sockfd, buf, strlen(buf), 0);
memset(buf, '\0', sizeof(buf));
//接收
ssize_t recv_size = recv(sockfd, buf, sizeof(buf) - 1, 0);
if(recv_size < 0)
{
perror("recv");
continue;
}
else if(recv_size == 0)
{
printf("peer close connect\n");
close(sockfd);
continue;
}
printf("%s\n", buf);
sleep(1);
}
close(sockfd);
return 0;
}
父进程对于子进程退出的处理是方式默认处理,即忽略不处理,这在子进程退出时会导致大量的僵尸进程
当打开一个客户端,ctrl+c掉,再打开另外一个客户端,继续ctrl+c掉,这样就会出现两个僵尸进程,当打开的客户端较多时,造成较多的僵尸进程会占用系统太多的资源
解决:改变父进程对子进程退出时的处理方式
signal(SIGCHLD, sigcallback);
void sigcallback(int signo)
{
wait(NULL);
}