在之前写的TCP网络程序中,我们用多进程的方式实现了一个服务器为多个用户提供服务。
简单TCP网络程序
链接://TODO
今天来实现一个服务器可以为多个用户提供服务。
接口之前已经介绍过了,今天在此处不再介绍,直接贴上代码
server |
#include <stdio.h>
#include <error.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
//简单TCP网络程序,可以实现简单聊天室
//******************服务器端********************
//TCP协议是建立连接的可靠传输协议
//1.绑定Ip 和 端口号
//2.被动打开,使可以被建立连接
//3.接受客户端的连接请求
//开始进行事件循环
// 4.接收来自客户端的请求
// 5.开始计算处理客户端发来的请求
// a)因为这里是回显服务,没有过多的计算处理,只做以下处理
// b)将客户端的请求显示在标准输出上
// c)再将请求发回给客户端
// 6.对客户端发来的请求进行响应
//用多线程来实现一个服务器可以同时处理多个用户的请求和响应
//需要注意以下几个小点
//1.线程入口函数中的参数应该是堆上开辟的,不能是栈上或者全局的
//2.主线中应该处理对其他线程的资源的回收
//这里不能使用线程等待函数,因为线程等待函数是阻塞式等待
//这样就不能达到同时处理多个用户的请求和响应
//应该采用线程分离,使内核自动回收线程资源
//3.应该处理文件描述符的关闭问题
//因为同组线程之间是共用一块虚拟地址空间,共享文件描述符表
//所以只要在一个线程任务处理完之后关闭文件描述符即可
typedef struct Info
{
sockaddr_in _peer;
int fd;
}Info;
void * Entry(void * arg)
{
Info * info =(Info *)arg;
sockaddr_in peer =info->_peer;
int new_socket=info->fd;
printf("Entry:%s:%d\n",inet_ntoa(info->_peer.sin_addr),info->fd);
char buf[1024]={0};
//用来处理一次连接。需要循环的处理
while(1)
{
//7.接收来自客户端的请求
ssize_t read_size=read(new_socket,buf,sizeof(buf)-1);
if(read_size<0)
{
continue;
}
if(read_size==0)
{
printf("client discount!\n");
//用来标识用户已经关闭连接
close(new_socket);
return NULL;
}
buf[read_size]='\0';
//8.将客户端的请求显示再标准输出上
printf("peer %s:%d say %s",inet_ntoa(peer.sin_addr),ntohs(peer.sin_port),buf);
//9.将请求发送给客户端
write(new_socket,buf,read_size);
printf("写会客户端\n");
}
return NULL;
}
void ProcessConnect(int new_socket,sockaddr_in peer)
{
printf("参数:%s :%d\n",inet_ntoa(peer.sin_addr),new_socket);
pthread_t pthread;
//线程入口函数中参数应该是堆上开辟的
Info *info=(Info *)malloc(sizeof(Info));
info->fd=new_socket;
info->_peer=peer;
printf("准备给线程传参:%s:%d\n",inet_ntoa(info->_peer.sin_addr),info->fd);
pthread_create(&pthread,0,Entry,info);
//回收线程的资源
pthread_detach(pthread);
return;
}
// ./service [IP] [Port]
int main(int argc,char * argv[])
{
//1.检查命令行参数
if(argc!=3)
{
printf("Usage: ./service [IP] [Port]\n");
return 1;
}
//2.创建socket
int fd=socket(AF_INET,SOCK_STREAM,0);
if(fd<0)
{
perror("socket");
return 1;
}
//3.将要绑定的IP 和 Prot 存储在 sockaddr_in 结构体中
sockaddr_in addr;
addr.sin_family=AF_INET;
socklen_t addr_len=sizeof(addr);
//将点分十进制的IP地址 转化 为32位数字
addr.sin_addr.s_addr=inet_addr(argv[1]);
//将端口号转位网络字节序
addr.sin_port=htons(atoi(argv[2]));
//4.服务器建立连接
int bind_ret=bind(fd,(sockaddr *)&addr,addr_len);
if(bind_ret<0)
{
perror("bind");
return 1;
}
//5.服务器开始被动打开,处于可以被建立连接状态
int listen_ret=listen(fd,5);
if(listen_ret<0)
{
perror("listen");
return 1;
}
//开始事件循环
while(1)
{
//6.接受客户端的连接请求
int new_socket=accept(fd,(sockaddr *)&addr,&addr_len);
//这里的返回值是一个新的 socket 将内核态建立的连接放到用户态执行
//后续信息传输建立在这个socket
if(new_socket<0)
{
perror("accept");
continue;
}
//7.处理一次连接
ProcessConnect(new_socket,addr);
}
close(fd);
return 0;
}
client |
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
//********************简单TCP网络程序
//********************客户端*************************
//
//TCP协议是建立连接的可靠传输
//1.与服务器建立连接
//2.向服务器发送请求
//3.接收来自服务器端响应
int main(int argc ,char *argv[])
{
//检查命令行参数
if(argc!=3)
{
printf("Usage : ./client [IP] [Port]\n");
return 1;
}
//1.创建socket
int fd=socket(AF_INET,SOCK_STREAM,0);
if(fd<0)
{
perror("socket");
return 1;
}
//2.建立连接
//将要建立连接的服务器的IP 和 Port 放在结构体中
sockaddr_in service;
service.sin_family=AF_INET;
socklen_t service_len=sizeof(service);
service.sin_addr.s_addr=inet_addr(argv[1]);
service.sin_port=htons(atoi(argv[2]));
int connect_ret=connect(fd,(sockaddr *)&service,service_len);
if(connect_ret<0)
{
perror("connect");
return 1;
}
char buf[1024]={0};
while(1)
{
//3.向服务器发送请求
//从标准输入开始读取数据
ssize_t read_size=read(0,buf,sizeof(buf)-1);
if(read_size<0)
{
perror("read");
return 1;
}
if(read_size==0)
{
printf("done\n");
return 0;
}
buf[read_size]='\0';
//将数据发送给服务器
write(fd,buf,read_size);
//4.从服务器接收请求
//构造结构体存放接收方的信息
//这里的service会作为输出型参数,因为服务器端是任意Ip主机都保持连接状态,这里接收服务的参数,然后打印出来
char buf_service[1024]={0};
read(fd,buf_service,sizeof(buf_service)-1);
if(read_size<0)
{
perror("recvfrom");
return 1;
}
if(read_size==0)
{
printf("read done\n");
return 0;
}
buf_service[read_size]='\0';
//5.将从服务器接收的信息显示到标准输出
printf("service[%s:%d] response :%s",inet_ntoa(service.sin_addr),ntohs(service.sin_port),buf_service);
}
}
用多线程来实现一个服务器可以同时处理多个用户的请求和响应
需要注意以下几个小点
1. 线程入口函数中的参数应该是堆上开辟的,不能是栈上或者全局的
2. 主线中应该处理对其他线程的资源的回收
这里不能使用线程等待函数,因为线程等待函数是阻塞式等待
这样就不能达到同时处理多个用户的请求和响应
应该采用线程分离,使内核自动回收线程资源
3. 应该处理文件描述符的关闭问题
因为同组线程之间是共用一块虚拟地址空间,共享文件描述符表
所以只要在一个线程任务处理完之后关闭文件描述符即可