1. 基于TCP的服务器编程模型
#ifndef __T_NET_H__
#define __T_NET_H__
#include
#include
typedef struct sockaddr SA;
typedef struct sockaddr_in SA4;
//socket bind
//这里将套接字的创建过程和绑定过程封装为一个函数
int bind_sock(int domain,int type,u_int16_t port);
//调用这个函数可以从未连接队列中取出一个进行处理.不会显示
//客户端的ip地址
int n_acpt(int fd);
//对比上一个函数,可以显示客户端的ip地址
int h_acpt(int fd);
//将套接字的创建 绑定 监听封装到一个函数中
int s_listen(int domain,int type,u_int16_t port,int b);
#endif //__T_NET_H__
编写完头文件,可以把头文件移动到/usr/local/include目录下.这样include可以使用<>
t_net.c源码
#include
#include
#include
#include
#include
#include
#include
int bind_sock(int domain,int type,u_int16_t port){
//创建一个socket设备,返回该设备的文件描述符
SA4 serv;
int sfd = socket(domain,type,0);
if(sfd == -1){
printf("%s\n",strerror(errno));
return -1;
}
//将sfd绑定绑定本地地址
serv.sin_family = AF_INET;
//主机字节序到网络字节序
serv.sin_port = htons(port);
serv.sin_addr.s_addr = htonl(INADDR_ANY);
int b = bind(sfd,(SA*)&serv,sizeof(serv));
if(b == -1){
printf("%s\n",strerror(errno));
return -1;
}
return sfd;
//不显示显示客户端的IP
int n_acpt(int fd){
int cfd = accept(fd,NULL,NULL);
if(cfd == -1){
printf("%s\n",strerror(errno));
}
return cfd;
}
//显示客户端的IP地址
int h_acpt(int fd){
SA4 cli;
char ip[32];
socklen_t len = sizeof(cli);
int cfd = accept(fd,(SA*)&cli,&len);
if(cfd == -1){
printf("%s\n",strerror(errno));
return -1;
}
printf("%s\n",inet_ntop(AF_INET,&cli.sin_addr,ip,32));
return cfd;
}
编写完源文件,也可把源文件封装成动态库
具体步骤如下:
1)生成与文件无关的可执行文件
gcc - c -fPIC t_net.c
2)打包动态库
gcc -shared -o libt_net.so t_net.o
3)将动态库移动到/lib目录下.
在编译的时候加上-lt_net即可
2 服务器的数据处理
#include
#include
#include
int t_main(int cfd){
//读取客户端的请求消息,read阻塞
char buf[128];
while(1){
int r = read(cfd,buf,128);
for(int i = 0;i < r ;i++)
buf[i] = toupper(buf[i]);
write(cfd,buf,r);
if(strcmp(buf,"BYEBYE") == 0) break;
}
return 0;
}
服务器的数据处理部分很简单,就是使用read函数读取客户段中的数据.并将服务端中的数据写回给客户端
3) 并发部分的实现
当一个客户端与服务器建立连接之后,在断开连接之前.服务器无法从未决连接队列中与其他的客户端建立连接.因此,需要一定手段来实现并发.
有三种方式:多线程 多进程 多路复用 epoll
这里使用的是多进程
父进程的任务
① 负责从未决连接队列中取出一个进行连接处理,返回一个连接描述符.
②创建子进程,子进程继承父进程的文件描述符.
③ 关闭连接描述符.
④负责回收子进程的资源.
子进程负责的任务
① 关闭设备描述符
② 使用连接描述符处理客户的业务
③处理完毕,关闭连接描述符
④ exit(0)
相关代码
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
//子进程终止时给父进程发送SIGCHLD信号
void handle(int n){
//回收子进程的资源
wait(NULL);
return ;
}
extern int t_main(int cfd);
int main(void){
//创建一个socket设备,返回该设备的文件描述符
signal(SIGCHLD,handle);
char buffer[128];
SA4 cli;
int s_fd = s_listen(AF_INET,SOCK_STREAM,8000,5);
if(s_fd == -1){
return -1;
}
while(1){
int cfd = h_acpt(s_fd);
if(cfd == -1) return -1;
pid_t pid = fork();
//父子进程是异步的
if(pid == -1){
printf("%s\n",strerror(errno));
return -1;
}
if(pid == 0){
close(s_fd);
t_main(cfd);
close(cfd);
exit(0);
}
else{
close(cfd);
//阻塞等待
//waitpid(-1,NULL,WNOHANG);
}
}
close(s_fd);
return 0;
}
这里使用一个信号函数,在子进程终止的时候会发射SIGCHILD信号,通知父进程回收子进程的资源.
3 TCP客户端的编程模型
1)创建socket设备socket(2);
2)绑定IP地址和端口号bind(2);
3)和服务器建立连接
4)循环处理数据 write(2) read(2)
5)关闭本次连接 close(2)
相关代码:
#include
#include
#include
#include
#include
#include
int main(int argc,char* argv[]){
//创建套接字
char msg[128];
if(argc < 2){
printf("参数过少\n");
return 0;
}
int cfd = socket(AF_INET,SOCK_STREAM,0);
if(cfd == -1){
printf("%s\n",strerror(errno));
return 0;
}
//向服务器发起请求
SA4 addr;
memset(&addr,0,sizeof(addr));
addr.sin_family = AF_INET;
//端口号
addr.sin_port = htons(8000);
char *ipaddress = argv[1];
struct in_addr ip;
inet_pton(AF_INET,ipaddress,&ip);
addr.sin_addr = ip;
int ret = connect(cfd,(SA*)&addr,sizeof(SA));
if(ret == 0){
printf("连接成功...\n");
}
else{
printf("连接失败...\n");
return 0;
}
while(1){
gets(msg);
write(cfd,msg,sizeof(msg));
char rbuf[128];
int r = read(cfd,rbuf,sizeof(rbuf));
printf("message from server:\n");
printf("%s\n",rbuf);
if(strcmp(rbuf,"BYEBYE") == 0) break;
}
close(cfd);
return 0;
}