TCP并发服务器的编程实现

TCP并发服务器的编程实现

1. 基于TCP的服务器编程模型

  1. 创建通信端点(套接字),返回该端点的文件描述符 sfd socket(2)
    2 )将sfd和本地的ip地址和端口号绑定 bind(2);
    3 )将sfd设置为被动连接状态,监听客户端的到来,如果有客户段的到来,将其放入到未决连接队列中.listen(2)
    while{
    4 从未决连接中取出一个处理,返回一个新的连接描述符 如果未决连接为空,阻塞等待 cfd = accept(2)
    5 从连接描述符中读取客户段的请求数据到buf中 read(2)
    6 处理buf中的数据
    7 将处理的结果写给客户端 write(2)
    8 关闭本次连接close(cfd)
    }
    2. TCP并发服务器分三个部分实现:
    1 网络编程部分:
    网络编程部分封装为一个源文件和头文件,分别是t_net.h t_net.c
    t_net.h部分源码
#ifndef __T_NET_H__
#define __T_NET_H__
#include <sys/socket.h>
#include <sys/types.h>
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 <t_net.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <errno.h>
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 <unistd.h>
#include <ctype.h>
#include <string.h>
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 <t_net.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <errno.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <stdlib.h>
#include <signal.h>

//子进程终止时给父进程发送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 <t_net.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <errno.h>

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;
}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值