提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
基于昨天实现的程序TCP的编程之socket套接字实现两台主机的无线通信今天实现了更进阶的功能
让多个客户端接入服务器,并可以实现同时发送数据给服务器
一、思路
首先并发服务器是能够同时处理多个客户端请求的服务器。既然要同时处理多个客户端的请求,那就应该引入进程\线程来实现,从而提高服务器的并发性和吞吐量。TCP服务器建立的前几步和上一篇文章是一样的。重点是最后怎么处理创建出来的进程,接下来我会贴出代码并且逐行讲解。
二、实现
1.服务端代码
代码如下(示例):
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
void *admin_send_SIG(void *arg);//线程函数
void *bf_sth(void *arg)//进程函数
{
int fd=*(int *)arg;
//植入线程
pthread_t tid;
pthread_create(&tid,NULL,admin_send_SIG,&fd);
char buf[20];
char buff[50]="服务器已接收\xF0\x9F\x98\x81";
while(1){
bzero(buf,sizeof(buf));
recv(fd,buf,sizeof(buf),0); //读取信息
printf("%s\n",buf);
send(fd,buff,sizeof(buff),0); //读完以后给主机回复
}
}
void *admin_send_SIG(void *arg)//后台发送信息
{
int fd=*(int *)arg;
char buf[20];
char buff[100]="服务器向你喊话\xF0\x9F\x98\x81";
while(1){
bzero(buf,sizeof(buf));
send(fd,buff,strlen(buff),0);
scanf("%s",buf);
send(fd,buf,sizeof(buf),0);
}
}
int main(){
//1>建立socket连接
int sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd<0){
perror("socket");
return -1;
}
printf("sockfd:%d\n",sockfd);
//2>绑定IP和端口号*************!
struct sockaddr_in server;//声明结构体,并即将给里面成员赋值
server.sin_family=AF_INET;//表示使用IPv4地址协议
server.sin_port=htons(10086);//端口号 将12345转化为大端序
server.sin_addr.s_addr=inet_addr("192.168.60.119");//IP赋值
if(bind(sockfd,(struct sockaddr *)&server,sizeof(server))){
perror("bind");
return -1;
}
//3>监听--->服务器的保护机制
listen(sockfd,8);//当前这个服务器,同一时刻连接客户端的最大值为8;
//声明连接主机的IP地址和端口号
struct sockaddr_in client;
int len=sizeof(client);
int fd;
while(1){
printf("主机来咯\n");
fd=accept(sockfd,(struct sockaddr *)&client,&len); //持续检测是否有用户访问服务器 如果没有访问则阻塞
if(fd<0){
perror("accept");
return -1;
}else{ //有客户端接入会调用frok函数创建父子进程
//父进程会正常结束
//子进程留下持续读取信息
printf("连接的IP为:%s",inet_ntoa(client.sin_addr));
if(fork()==0){
bf_sth(&fd); //进入持续读取信息函数
exit(-1); //当整个程序结束后 回收子进程的资源
}
}
}
return 0;
}
1.1核心代码块.
代码如下:
struct sockaddr_in client; //声明一个主机地址结构体准备接受连接主机的地址信息
int len=sizeof(client);
int fd;
while(1){
printf("主机来咯\n");
fd=accept(sockfd,(struct sockaddr *)&client,&len); //持续检测是否有用户访问服务器 如果没有访问则阻塞
if(fd<0){
perror("accept");
return -1;
}else{ //有客户端接入会调用frok函数创建父子进程
//父进程会正常结束
//子进程留下持续读取信息
printf("连接的IP为:%s",inet_ntoa(client.sin_addr));
if(fork()==0){
bf_sth(&fd); //进入持续读取信息函数
exit(-1); //当整个程序结束后 回收子进程的资源
}
}
}
return 0;
}
该段代码块实现的逻辑功能是当服务器检测到有用户访问服务器后,会获取该用户的地址信息 并且赋值给fd然后代码根据获取到的用户信息开辟进程组,父进程正常死亡 子进程留下调用进程函数。
1.2进程函数
void *bf_sth(void *arg)//进程函数
{
int fd=*(int *)arg;
//植入线程
pthread_t tid;
pthread_create(&tid,NULL,admin_send_SIG,&fd);//创造线程并调用线程函数
char buf[20]; //存储要接收数据的中间数组
char buff[50]="服务器已接收\xF0\x9F\x98\x81"; //当检测到用户发送给服务器的信息返回一句话并附带上emoji
while(1){
bzero(buf,sizeof(buf)); //每次读取信息前都清空一次数组
recv(fd,buf,sizeof(buf),0); //读取信息
printf("%s\n",buf);
send(fd,buff,sizeof(buff),0); //读完以后给主机回复
}
}
进程函数实现了服务器(读取)与客户端(发送)的单向通信并开辟了一个新的线程 为服务器的发送功能做准备
1.3线程函数
void *admin_send_SIG(void *arg)//后台发送信息
{
int fd=*(int *)arg;
char buf[20];
char buff[100]="服务器向你喊话\xF0\x9F\x98\x81";
while(1){
bzero(buf,sizeof(buf));
send(fd,buff,strlen(buff),0);
scanf("%s",buf);
send(fd,buf,sizeof(buf),0);
}
}
开辟一个线程从而实现服务器的收发阻塞互不干扰
2.客户端
客户端的代码与昨天无异,主要是服务端上的代码做了修改
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/socket.h>
#include <stdlib.h>
/*建立sock套接字
* 声明服务器的IP端口号
* 主动连接服务器
* 收/发
* */
int main(int argc,char *argv[])
{
if(argc != 3){
printf("User:%s <IP><PORT>\n",argv[0]);
return -1;
}
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd < 0){
perror("socket");
return -1;
}
printf("sockfd = %d\n",sockfd);
//声明IP和端口号
struct sockaddr_in server;//声明结构体,并即将给里面的成员赋值
server.sin_family = AF_INET;//表示使用IPv4地址协议
server.sin_addr.s_addr = inet_addr(argv[1]);//ip赋值
server.sin_port = htons(atoi(argv[2]));//端口号 将12345转成大端序
//3>主动连接服务器
if(connect(sockfd,(struct sockaddr *)&server,sizeof(server))){
perror("connect");
return -1;
}
printf("连接成功\n");
//4>发
pid_t pid = fork();//开辟2个进程
char r_buf[50];
char w_buf[50];
if(pid > 0){
while(1){
printf("我是父进程\n");
bzero(w_buf,sizeof(w_buf));
scanf("%s",w_buf);
send(sockfd,w_buf,strlen(w_buf),0);
}
}
if(pid == 0){
while(1){
bzero(r_buf,sizeof(r_buf));
printf("我是子进程\n");
recv(sockfd,r_buf,sizeof(r_buf),0);
printf("%s\n",r_buf);
}
}
return 0;
}
总结
并口通信主要是在用户接入时引入了创建进程的函数,从而实现了每个用户都互不干扰并且能与服务器进行通信。不过该代码目前还不太完善,比如说无法做到在一个服务器中的两个用户单独的通信。