Linux网络编程笔记:socket编程(二)
一、概述
前面一篇笔记主要写了socket的基础知识,包括什么是socket,socket在网络模型中的位置,socket编程常用的函数,最后编写了一个简单的单客户端访问服务器通信的程序。
在实际的应用场景中,单客户端访问的机制显然不能满足需求的,要能够实现多个服务器能够同时访问的技术。可以利用多进程和多线程的方式实现多个客户端同时访问的机制。以下主要通过程序的形式,展现是如何实现的。
二、多进程实现
客户端程序不需要更改,跟前一篇内容相同。在创建多进程进行搭建服务器时,需要注意的是,子进程的回收。
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
#include<sys/stat.h>
#include<string.h>
#include <sys/socket.h>
#include<arpa/inet.h>
#include<ctype.h>
#include<sys/wait.h>
#include<signal.h>
#define MAX_LISTEN_NUM 10
void catch_child(int signum){ //捕捉函数
while(waitpid(0,NULL,WNOHANG) > 0);
return ;
}
int main(void){
int lfd,cfd;
int pid;
int ret,rd_ret;
char buf[1024],client_IP[1024];
struct sockaddr_in server_addr,client_addr;
socklen_t client_addr_len;
lfd = socket(AF_INET, SOCK_STREAM, 0); //创建socket
if(lfd == -1){
printf("socket failed\n");
exit(1);
}
server_addr.sin_family = AF_INET; //设置服务器IP+PORT
server_addr.sin_port = htons(5678);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
ret = bind(lfd, (struct sockaddr *)&server_addr, sizeof(server_addr)); //绑定
if(ret != 0){
printf("bind failed\n");
exit(1);
}
ret = listen(lfd, MAX_LISTEN_NUM); //设置监听数
if(ret != 0){
printf("listen failed\n");
exit(1);
}
client_addr_len = sizeof(client_addr);
while(1){
cfd = accept(lfd,(struct sockaddr *)&client_addr, &client_addr_len); //等待连接
pid = fork();
if(pid < 0){
printf("fork failed\n");
exit(1);
}
if(pid == 0){ //子进程 退出循环
close(lfd);
break;
}
else if (pid > 0){ //父进程 继续循环,去监听下一个连接者,并回收子进程
struct sigaction act;
act.sa_handler = catch_child;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
ret = sigaction(SIGCHLD,&act,NULL);
if(ret != 0){
close(cfd);
}
continue;
}
}
if(pid == 0){
while(1){
rd_ret = read(cfd,buf,sizeof(buf));
if(rd_ret == 0){ //已经读到结尾(对端已经关闭)
close(lfd);
exit(1);
}
write(STDOUT_FILENO, buf,rd_ret);
}
}
return 0;
}
客户端1写数据:
客户端2写数据:
服务器读数据
三、多线程实现
在多线程里,线程回收不像进程那样复杂,只要将创建的线程进行分离即可。
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
#include<sys/stat.h>
#include<string.h>
#include <sys/socket.h>
#include<arpa/inet.h>
#include<ctype.h>
#include<sys/wait.h>
#include<signal.h>
#define MAX_LISTEN_NUM 10
struct s_info { //定义一个结构体, 将地址结构跟cfd捆绑
struct sockaddr_in cliaddr;
int connfd;
};
void *do_work(void *arg)
{
int n,i;
struct s_info *ts = (struct s_info*)arg;
char buf[1024];
char str[INET_ADDRSTRLEN]; //#define INET_ADDRSTRLEN 16
while (1) {
n = read(ts->connfd, buf, sizeof(buf)); //读客户端
if (n == 0) {
printf("the client %d closed...\n", ts->connfd);
break; //跳出循环,关闭cfd
}
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &(*ts).cliaddr.sin_addr, str, sizeof(str)),
ntohs((*ts).cliaddr.sin_port)); //打印客户端信息(IP/PORT)
write(STDOUT_FILENO, buf, n); //写出至屏幕
}
close(ts->connfd);
return (void *)0;
}
int main(void)
{
struct sockaddr_in servaddr, cliaddr;
socklen_t cliaddr_len;
int listenfd, connfd;
int ret;
pthread_t tid;
struct s_info ts[256]; //根据最大线程数创建结构体数组.
int i = 0;
listenfd = socket(AF_INET, SOCK_STREAM, 0); //创建socket
if(listenfd == -1){
printf("socket failed\n");
exit(1);
}
servaddr.sin_family = AF_INET; //设置服务器IP+PORT
servaddr.sin_port = htons(5678);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
ret = bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); //绑定
if(ret != 0){
printf("bind failed\n");
exit(1);
}
ret = listen(listenfd, MAX_LISTEN_NUM); //设置监听数
if(ret != 0){
printf("listen failed\n");
exit(1);
}
printf("Accepting client connect ...\n");
while (1) {
cliaddr_len = sizeof(cliaddr);
connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len); //阻塞监听客户端链接请求
ts[i].cliaddr = cliaddr;
ts[i].connfd = connfd;
/* 达到线程最大数时,pthread_create出错处理, 增加服务器稳定性 */
pthread_create(&tid, NULL, do_work, (void*)&ts[i]);
pthread_detach(tid); //子线程分离,防止僵线程产生.
i++;
}
return 0;
}
客户端1写数据:
客户端2写数据:
服务器读数据: