实现TCP服务器的单进程版本,多进程版本,多线程版本。
所需API如下:
1 创建socket文件描述符(TCP/UDP, 客户端+服务器)
int socket(int domain, int type, int protocol);
•对于IPv4, family参数指定为AF_INET
•对于TCP协议,type参数指定为SOCK_STREAM,表示面向字节流的传输协议
•protocol参数指定为0即可
2 绑定端口号(TCP/UPD ,服务器)
int bind(int socket, const struct sockaddr* address, socklen_t address_len)
服务器程序监听的网络地址和端口号通常固定不变,
服务器需要调用bind绑定一个固定的网络地址和端口号
•struct sockaddr *是⼀个通用指针类型,address参数实际上可以接受多种协议的sockaddr结构体,第三个参数address_len指定结构体的⻓度
3 开始监听socket(TCP , 服务器)
int listen(int socket, int backlog)
listen()声明socket处于监听状态,最多允许有backlog个客户端处于连接等待状态
如果接收更多的连接请求就忽略,设置一般不会太大(为5)
4 接收请求(TCP 服务器)
int accept (int socket, struct sockaddr* address, socklen_t* address_len);
三次握手后服务器调用accept接收连接
如果调用accept时还没有客户端的连接请求,就阻塞等待直到客户端连接上来
adress是一个输出型参数,accept()返回时传出客户端的地址和端口号
如果给adddress参数传NULL, 表示不关心客户端地址
•address_len参数是⼀个传⼊传出参数(value-result argument),传⼊的是调⽤者提供的,缓冲区address的长度以避免缓冲区溢出问题,传出的是客户端地址结构体的实际⻓度(有可能没有占满调⽤者提供的缓冲区)
5 建立连接(TCP, 客户端)
int connect(int sockfd, const struct sockaddr*addr, socklen_t addrlen);
客户端调用connect连接服务器
•connect和bind的参数形式⼀致,区别在于bind的参数是自己的地址,而connect的参数是对方的地址
客户端
/////////////////////////////
//客户端处理
//1 从标准输入读入字符串
//2 把读入的字符串发送给服务器
//3 尝试从服务器读取响应数据
//4 把响应结果打印到标准输出上
///////////////////////////
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;
int main(int argc, char* argv[])
{
if(argc!=3)
{
printf("usage ./server [ip][port]\n");
return 1;
}
// 1 创建socket
int fd=socket(AF_INET, SOCK_STREAM,0);
if(fd<0)
{
perror("socket");
return 1;
}
//2 获取连接
sockaddr_in server_addr;
server_addr.sin_family=AF_INET;
server_addr.sin_addr.s_addr=inet_addr(argv[1]);
server_addr.sin_port=htons(atoi(argv[2]));
int ret=connect(fd, (sockaddr*)&server_addr, sizeof(server_addr));
if(ret<0)
{
perror("connect");
return 1;
}
// 进入循环
while(1)
{
//a从标准输入读入字符串
char buf[1024]={0};
ssize_t read_size=read(0,buf,sizeof(buf)-1);
if(read_size<0)
{
perror("read");
return 1;
}
if(read_size==0)
{
printf("read done!\n");
return 0;
}
buf[read_size]='\0';
//b)把读入的字符串发送给服务器
write(fd, buf, strlen(buf));
//c)尝试从服务器读取响应数据
char buf_resp[1024]={0};
read_size =read(fd, buf_resp, sizeof(buf_resp)-1);
if(read_size<0)
{
perror("read_size");
return 1;
}
if(read_size==0)
{
printf("server close socket!\n");
return 0;
}
buf_resp[read_size]='\0';
//d)把响应结果打印到标准输出上
printf("server resp: %s\n",buf_resp);
}
return 0;
}
服务端单进程版
//服务器基本流程
//1.从socket中读取数据(Request)
//2,根据(Request)计算生成Response
//3.把Response 写回客户端
//目前实现的是 echo_server, 计算生成Response的步骤就省略了
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;
void ProcessConnect(int new_sock)
{
//完成一次连接的处理
//需要循环的来处理客户福安发送发数据
while(1)
{
//子进程
//a)从客户端读取数据
char buf[1024]={0};
ssize_t read_size=read(new_sock,buf,sizeof(buf)-1);
if(read_size<0)
{
perror("read");
continue;
}
if(read_size==0)
{
//TCP中,如果read的返回值是0,说明对端关闭了连接
printf("[client %d] disconnect!\n",new_sock);
close(new_sock);
return ;
}
buf[read_size]='\0';
//b) 根据请求计算响应(省略)
printf("[client %d] %s\n", new_sock, buf);
//c) 把响应写回客户端
write(new_sock, buf,strlen(buf));
}
}
// ./server [ip] [port]
int main( int argc, char* argv[] )
{
if(argc!=3)
{
printf("Usage ./server [ip] [port]");
return 1;
}
//1.创建 socket
int server_sock=socket(AF_INET, SOCK_STREAM, 0);
if(server_sock<0)
{
perror("socket");
return 1;
}
//2绑定端口号
sockaddr_in server;
server.sin_family=AF_INET;
server.sin_addr.s_addr=inet_addr(argv[1]);
server.sin_port=htons(atoi(argv[2]));
int ret=bind(server_sock, (sockaddr*)&server,sizeof(server));
if(ret<0)
{
perror("bind");
return 1;
}
//3使用listen允许服务器被客户端连接
ret=listen(server_sock,5);
if(ret<0)
{
perror("listen");
return 1;
}
//4服务器初始化完成,进入事件循环
printf("Server Init ok!\n");
while(1)
{
sockaddr_in peer;
socklen_t len=sizeof(peer);
int new_sock=accept(server_sock, (sockaddr*)&peer, &len);
if(new_sock<0)
{
perror("accept");
continue;
}
printf("[client %d] connect!\n", new_sock);
ProcessConnect(new_sock);
}
return 0;
}
服务端多进程版
/
//服务器基本流程(多进程)
//1 从sock中读取数据(请求)
//2 根据请求计算生成(回应)
//3 把回应写到客户端
//由于目前实现的是echo_server,省略请求的计算
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/socket.h>
#include<signal.h>
#include<netinet/in.h>
#include<arpa/inet.h>
typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;
void ProcessConnect(int new_sock, sockaddr_in* peer)
{
while(1)
{
int ret=fork();
if(ret<0)
{
perror("fork");
return ;
}
if(ret>0)
{
//父进程,考虑僵尸进程的问题,wait,waitpid是不行的
//简单方案是忽略 SIGCHLD信号
signal(SIGCHLD, SIG_IGN);
//文件描述符需要父子进程都关闭
close(new_sock);
return ;
}
//子进程
//a 从客户端读取数据
char buf[1024]={0};
ssize_t read_size=read(new_sock, buf, sizeof(buf)-1);
if(read_size<0)
{
perror("read");
continue;
}
if(read_size==0)
{
//TCP中,read的返回值为0说明对端关闭了连接
printf("[client %s: %d] disconnect!\n", inet_ntoa(peer->sin_addr),ntohs(peer->sin_port));
close(new_sock);
// 注意!!!、
// 不能直接让函数返回,让子进程直接退出,
// 如果是函数返回,子进程也会尝试进行accept
// 这样的动作是没有必要的,父进程已经负责了accept
//子进程只要把对应的客户端服务好就行了
exit(0);
}
buf[read_size]='\0';
//根据请求计算响应(忽略)
printf("[client %s:%d] %s\n",inet_ntoa(peer->sin_addr),ntohs(peer->sin_port), buf);
//把响应写回到客户端
write(new_sock, buf,strlen(buf));
}
}
// ./server [ip] [port]
int main(int argc, char* argv[])
{
if(argc!=3)
{
printf("usage ./server[ip] [port]\n");
return 1;
}
//1.创建 socket
int sock=socket(AF_INET, SOCK_STREAM,0);
if(sock<0)
{
perror("socket");
return 1;
}
//2 绑定端口号
sockaddr_in server;
server.sin_family=AF_INET;
server.sin_addr.s_addr=inet_addr(argv[1]);
server.sin_port=htons(atoi(argv[2]));
int ret=bind(sock, (sockaddr*)&server, sizeof(server));
if(ret<0)
{
perror("bind");
return 1;
}
//3 使用listen允许服务器被客户端连接
ret=listen(sock, 5);
if(ret<0)
{
perror("bind");
return 1;
}
//4 服务器初始化完成,进入事件循环
printf("Server Init OK!\n");
while(1)
{
sockaddr_in peer;
socklen_t len=sizeof(peer);
int new_sock=accept(sock,(sockaddr*)&peer, &len);
if(new_sock<0)
{
perror("accept");
continue;
}
printf("[clien %s:%d] connect!\n", inet_ntoa(peer.sin_addr),ntohs(peer.sin_port));
ProcessConnect(new_sock, &peer);
}
return 0;
}
服务端多线程版
//服务器基本流程
//从socket中读取数据(请求)
//2 根据请求计算生成 (回应)
//3 把回应写入到客户端
//由于目前实现的是 echo_server。省略 回应的步骤
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<pthread.h>
typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;
void* ThreadEntry(void* arg)
{
int new_sock=(int)arg;
while(1)
{
//a 从客户端读取数据
char buf[1024]={0};
ssize_t read_size=read(new_sock, buf, sizeof(buf)-1);
if(read_size<0)
{
perror("read");
continue;
}
if(read_size==0)
{
//TCP中说明,对端关闭了连接
printf("[client %lu] disconnetc!\n", new_sock);
close(new_sock);
return NULL;
}
buf[read_size]='\0';
//b 根据请求计算响应(忽略)
printf("[client %lu] %s \n", new_sock, buf);
//c 把响应写回到客户端
write(new_sock, buf,strlen(buf));
}
return NULL;
}
void ProcessConnect(int new_sock)
{
//创建线程完成和客户端的交互
//
//完成一次连接的处理
//需要循环的处理客户端发送来的数据
pthread_t tid;
pthread_create(&tid, NULL,ThreadEntry,(void*)new_sock);
pthread_detach(tid);
}
int main(int argc, char* argv[])
{
if(argc!=3)
{
printf("usage ./server [ip] [port]\n");
return 1;
}
//1 创建socket
int listen_sock=socket(AF_INET,SOCK_STREAM,0);
if(listen_sock<0)
{
perror("socket");
return 1;
}
//2 绑定端口号
sockaddr_in server;
server.sin_family=AF_INET;
server.sin_addr.s_addr=inet_addr(argv[1]);
server.sin_port=htons(atoi(argv[2]));
int ret=bind(listen_sock, (sockaddr*)&server, sizeof(server));
if(ret<0)
{
perror("bind");
return 1;
}
//3 使用listen 允许服务器被客户端连接
ret =listen(listen_sock, 5);
if(ret<0)
{
perror("listen");
return 1;
}
//4 服务器初始化完成,进入事件循环
printf("Server Init OK!\n");
while(1)
{
sockaddr_in peer;
socklen_t len=sizeof(peer);
int new_sock=accept(listen_sock, (sockaddr*)&peer, &len);
if(new_sock<0)
{
perror("accept");
continue;
}
printf("[client %d] connect!\n", new_sock);
ProcessConnect(new_sock);
}
return 0;
}