55.1 TCP 连接和关闭过程

55.1.1 介绍

  

  建立连接的过程就是三次握手的过程:客户端发送 SYN 报文给服务器,服务器回复 SYN+ACK 报文,客户机再发送 ACK 报文。

  关闭连接的过程:客户机先发送 FIN 报文,服务器回复 ACK 报文,服务器再发送 FIN 报文,客户机再发送响应报文 ACK。

55.1.2  自定义协议编程例子 

  msg.h

 #ifndef __MSG_H__
#define __MSG_H__ #include <sys/types.h> typedef struct {
/** 协议头部: 不传输任何数据,只包含发送端的一些信息 */
char head[]; ///< 协议头部
char checknum; ///< 校验码 /**协议体部 */
char buff[]; ///< 数据
}Msg; /** 发送一个基于自定义协议的 message,发送的数据存放在 buff 中 */
extern int write_msg(int sockfd, char *buff, ssize_t len); /** 读取一个基于自定义协议的 message, 读取的数据存放在 buff 中 */
extern int read_msg(int sockfd, char *buff, ssize_t len); #endif

  msg.c

 #include "msg.h"
#include <unistd.h>
#include <string.h>
#include <memory.h>
#include <sys/types.h> /** 计算校验码 */
static unsigned char msg_check(Msg *message)
{
unsigned char s = ;
int i;
for(i = ; i < sizeof(message->head); i++){
s += message->head[i];
} for(i = ; i < sizeof(message->buff); i++){
s += message->buff[i];
} return s;
} /** 发送一个基于自定义协议的 message,发送的数据存放在 buff 中 */
int write_msg(int sockfd, char *buff, ssize_t len)
{
Msg message;
memset(&message, , sizeof(message));
strcpy(message.head, "hello");
memcpy(message.buff, buff, len);
message.checknum = msg_check(&message); if(write(sockfd, &message, sizeof(message)) != sizeof(message)){
return -;
} return ;
} /** 读取一个基于自定义协议的 message, 读取的数据存放在 buff 中 */
int read_msg(int sockfd, char *buff, ssize_t len)
{
Msg message;
memset(&message, , sizeof(message)); ssize_t size;
if((size = read(sockfd, &message, sizeof(message))) < ){
return -;
}
else if(size == ){
return ;
} /** 进行校验码验证,判断接收到的 message 是否完整 */
unsigned char s = msg_check(&message);
if((s == (unsigned char)message.checknum) && (!strcmp("hello", message.head))){
memcpy(buff, message.buff, len);
return sizeof(message);
}
return -; }

  编译成 .o 文件:gcc -o obj/msg.o -Iinclude -c src/msg.c

55.2 服务器的并发过程

55.2.1 介绍

  一个服务器处理多个客户端的请求,就称为服务器的并发。

  • 服务器端并发性处理

    • 多进程模型
    • 多线程模型
    • I/O多路转换(select)

  

55.2.2  基于自定义协议的多进程模型编程

(1)服务器代码

  echo_tcp_server.c

 #include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <signal.h>
#include <time.h>
#include <arpa/inet.h>
#include <errno.h>
#include "msg.h" int sockfd; void sig_handler(int signo)
{
if(signo == SIGINT){
printf("server close\n");
/** 步骤6: 关闭 socket */
close(sockfd);
exit();
} if(signo == SIGINT){
printf("child process deaded...\n");
wait();
}
} /** 输出连接上来的客户端相关信息 */
void out_addr(struct sockaddr_in *clientaddr)
{
/** 将端口从网络字节序转换成主机字节序 */
int port = ntohs(clientaddr->sin_port);
char ip[];
memset(ip, , sizeof(ip));
/** 将 ip 地址从网络字节序转换成点分十进制 */
inet_ntop(AF_INET, &clientaddr->sin_addr.s_addr, ip, sizeof(ip));
printf("client: %s(%d) connected\n", ip, port);
} void do_service(int fd)
{
/** 和客户端进行读写操作(双向通信) */
char buff[];
while(){
memset(buff, , sizeof(buff));
printf("start read and write....\n");
ssize_t size;
if((size = read_msg(fd, buff, sizeof(buff))) < ){
perror("protocal error");
break;
}
else if(size == ){
break;
}
else {
printf("%s\n", buff);
if(write_msg(fd, buff, sizeof(buff)) < ){
if(errno == EPIPE){
break;
}
perror("protocal error");
}
}
}
} int main(int argc, char *argv[])
{
if(argc < ){
printf("usage: %s #port\n", argv[]);
exit();
} if(signal(SIGINT, sig_handler) == SIG_ERR){
perror("signal sigint error");
exit();
} if(signal(SIGCHLD, sig_handler) == SIG_ERR){
perror("signal sigchld error");
exit();
} /** 步骤1: 创建 socket(套接字)
* 注: socket 创建在内核中,是一个结构体.
* AF_INET: IPV4
* SOCK_STREAM: tcp 协议
* AF_INET6: IPV6
*/
sockfd = socket(AF_INET, SOCK_STREAM, );
if(sockfd < ){
perror("socket error");
exit();
} /**
* 步骤2: 调用 bind 函数将 socket 和地址(包括 ip、port)进行绑定
*/
struct sockaddr_in serveraddr;
memset(&serveraddr, , sizeof(struct sockaddr_in));
/** 往地址中填入 ip、port、internet 地址族类型 */
serveraddr.sin_family = AF_INET; ///< IPV4
serveraddr.sin_port = htons(atoi(argv[1])); ///< 填入端口
serveraddr.sin_addr.s_addr = INADDR_ANY; ///< 填入 IP 地址
if(bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr_in))){
perror("bind error");
exit();
} /**
* 步骤3: 调用 listen 函数启动监听(指定 port 监听)
* 通知系统去接受来自客户端的连接请求
* (将接受到的客户端连接请求放置到对应的队列中)
* 第二个参数: 指定队列的长度
*/
if(listen(sockfd, ) < ){
perror("listen error");
exit();
} /**
* 步骤4: 调用 accept 函数从队列中获得一个客户端的请求连接, 并返回新的
* socket 描述符
* 注意: 若没有客户端连接,调用此函数后会足则, 直到获得一个客户端的连接
*/
struct sockaddr_in clientaddr;
socklen_t clientaddr_len = sizeof(clientaddr);
while(){
int fd = accept(sockfd, (struct sockaddr *)&clientaddr, &clientaddr_len);
if(fd < ){
perror("accept error");
continue;
} /**
* 步骤5: 启动子进程去调用 IO 函数(read/write)和连接的客户端进行双向的通信
*/
pid_t pid = fork();
if(pid < ){
continue;
}
else if(pid == ){
/** 子进程 */
out_addr(&clientaddr);
do_service(fd);
/** 步骤6: 关闭 socket */
close(fd);
break;
}
else{
/** 父进程 */
/** 步骤6: 关闭 socket */
close(fd);
}
} return ;
}

  gcc -o bin/echo_tcp_server -Iinclude obj/msg.o src/echo_tcp_server.c

(2)客户端代码

  echo_tcp_client.c

 #include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
#include <memory.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netdb.h>
#include <signal.h>
#include <string.h>
#include <time.h>
#include <arpa/inet.h>
#include "msg.h" int main(int argc, char *argv[])
{
if(argc < ){
printf("usage: %s ip port\n", argv[]);
exit();
} /** 步骤1: 创建 socket */
int sockfd = socket(AF_INET, SOCK_STREAM, );
if(sockfd < ){
perror("socket error");
exit();
} /** 往 serveraddr 中填入 ip、port 和地址族类型(ipv4) */
struct sockaddr_in serveraddr;
memset(&serveraddr, , sizeof(struct sockaddr_in));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(atoi(argv[]));
/** 将 ip 地址转换成网络字节序后填入 serveraddr 中 */
inet_pton(AF_INET, argv[], &serveraddr.sin_addr.s_addr); /**
* 步骤2: 客户端调用 connect 函数连接到服务器端
*/
if(connect(sockfd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr_in)) < ){
perror("connect error");
exit();
} /** 步骤3: 调用 IO 函数(read/write)和服务器端进行双向通信 */
char buff[];
ssize_t size;
char *prompt = "==>";
while(){
memset(buff, , sizeof(buff));
write(STDOUT_FILENO, prompt, );
size = read(STDIN_FILENO, buff, sizeof(buff));
if(size < ) continue;
buff[size - ] = '\0'; if(write_msg(sockfd, buff, sizeof(buff)) < ){
perror("write msg error");
continue;
}
else {
if(read_msg(sockfd, buff, sizeof(buff)) < ){
perror("read msg error");
continue;
}
else {
printf("%s\n", buff);
}
}
} /** 步骤4: 关闭 socket */
close(sockfd); return ;
}

  gcc -o bin/echo_tcp_client -Iinclude obj/msg.o src/echo_tcp_client.c

(3)测试

  开启两个终端进行测试,一个运行服务器,一个运行客户端:

  

  

  

55.2.3  基于自定义协议的多线程模型编程

  

  echo_tcp_server_th.c

 #include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <signal.h>
#include <time.h>
#include <arpa/inet.h>
#include <errno.h>
#include "msg.h"
#include <pthread.h> int sockfd; void sig_handler(int signo)
{
if(signo == SIGINT){
printf("server close\n");
/** 步骤6: 关闭 socket */
close(sockfd);
exit();
}
} void do_service(int fd)
{
/** 和客户端进行读写操作(双向通信) */
char buff[];
while(){
memset(buff, , sizeof(buff));
ssize_t size;
if((size = read_msg(fd, buff, sizeof(buff))) < ){
perror("protocal error");
break;
}
else if(size == ){
break;
}
else {
printf("%s\n", buff);
if(write_msg(fd, buff, sizeof(buff)) < ){
if(errno == EPIPE){
break;
}
perror("protocal error");
}
}
}
} void out_fd(int fd)
{
struct sockaddr_in addr;
socklen_t len = sizeof(addr);
/** 从 fd 中获得连接的客户端相关信息并放置到 sockaddr_in 当中 */
if(getpeername(fd, (struct sockaddr *)&addr, &len) < ){
perror("getpeername error");
return;
} char ip[];
memset(ip, , sizeof(ip));
int port = ntohs(addr.sin_port);
inet_ntop(AF_INET, &addr.sin_addr.s_addr, ip, sizeof(ip));
printf("%16s(%5d) closed!\n", ip, port);
} void *th_fn(void *arg)
{
int fd = (int)arg; do_service(fd);
out_fd(fd);
close(fd);
return (void *);
} int main(int argc, char *argv[])
{
if(argc < ){
printf("usage: %s #port\n", argv[]);
exit();
} if(signal(SIGINT, sig_handler) == SIG_ERR){
perror("signal sigint error");
exit();
} /** 步骤1: 创建 socket(套接字)
* 注: socket 创建在内核中,是一个结构体.
* AF_INET: IPV4
* SOCK_STREAM: tcp 协议
* AF_INET6: IPV6
*/
sockfd = socket(AF_INET, SOCK_STREAM, );
if(sockfd < ){
perror("socket error");
exit();
} /**
* 步骤2: 调用 bind 函数将 socket 和地址(包括 ip、port)进行绑定
*/
struct sockaddr_in serveraddr;
memset(&serveraddr, , sizeof(struct sockaddr_in));
/** 往地址中填入 ip、port、internet 地址族类型 */
serveraddr.sin_family = AF_INET; ///< IPV4
serveraddr.sin_port = htons(atoi(argv[1])); ///< 填入端口
serveraddr.sin_addr.s_addr = INADDR_ANY; ///< 填入 IP 地址
if(bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr_in))){
perror("bind error");
exit();
} /**
* 步骤3: 调用 listen 函数启动监听(指定 port 监听)
* 通知系统去接受来自客户端的连接请求
* (将接受到的客户端连接请求放置到对应的队列中)
* 第二个参数: 指定队列的长度
*/
if(listen(sockfd, ) < ){
perror("listen error");
exit();
} /**
* 步骤4: 调用 accept 函数从队列中获得一个客户端的请求连接, 并返回新的
* socket 描述符
* 注意: 若没有客户端连接,调用此函数后会足则, 直到获得一个客户端的连接
*/ /** 设置线程的分离属性 */
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); while(){
/** 主控线程负责调用 accept 去获得客户端的连接 */
int fd = accept(sockfd, NULL, NULL);
if(fd < ){
perror("accept error");
continue;
} /**
* 步骤5: 启动子线程去调用 IO 函数(read/write)和连接的客户端进行双向的通信
*/
pthread_t th;
int err;
/** 以分离状态启动子线程 */
if((err = pthread_create(&th, &attr, th_fn, (void *)fd)) != ){
perror("pthread create error");
} pthread_attr_destroy(&attr); } return ;
}

  客户端程序用上面的客户端程序即可。

五十五、linux 编程——TCP 连接和关闭过程及服务器的并发处理的更多相关文章

  1. 五十四 网络编程 TCP编程

    Socket是网络编程的一个抽象概念.通常我们用一个Socket表示“打开了一个网络链接”,而打开一个Socket需要知道目标计算机的IP地址和端口号,再指定协议类型即可. 客户端 大多数连接都是可靠 ...

  2. 五十三、linux 编程——TCP 编程基本介绍

    53.1 socket 套接字 53.1.1 介绍 Socket(套接字)是一种通讯机制,它包含一整套的调用接口和数据结构的定义,它给应用进程提供了使用如 TCP/UDP 灯网络协议进行网络通讯的手段 ...

  3. 孤荷凌寒自学python第五十五天初识MongoDb数据库

    孤荷凌寒自学python第五十五天第一天初识MongoDb数据库 (完整学习过程屏幕记录视频地址在文末) 大家好,2019年新年快乐! 本来我想的是借新年第一天开始,正式尝试学习爬虫,结果今天偶然发现 ...

  4. 第三百五十五天 how can I 坚持

    快一年了,三百五十五天了,等写个程序算算时间,看看日期和天数能不能对的上,哈哈. 计划还是未制定,天气预报还是没有写完,立马行动,发完这个博客,立马行动. 计划:设计模式1个月,三大框架3个月,计算机 ...

  5. 第三百五十五节,Python分布式爬虫打造搜索引擎Scrapy精讲—scrapy信号详解

    第三百五十五节,Python分布式爬虫打造搜索引擎Scrapy精讲—scrapy信号详解 信号一般使用信号分发器dispatcher.connect(),来设置信号,和信号触发函数,当捕获到信号时执行 ...

  6. “全栈2019”Java第五十五章:方法的静态绑定与动态绑定

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  7. 第13章 TCP编程(2)_TCP的连接和关闭过程

    4. TCP的连接和关闭过程 4.1 TCP连接的三次握手和四次挥手 (1)三次握手 ①第1次握手:建立连接.客户端发送连接请求报文段(SYN=1,sequence Number=x):然后客户端进入 ...

  8. python网络编程--TCP连接的三次握手(三报文握手)与四次挥手

    一.TCP连接 运输连接有三个阶段: 连接建立.数据传送和连接释放. 在TCP连接建立过程中要解决以下三个问题: 1,要使每一方能够确知对方的存在. 2.要允许双方协商一些参数(如最大窗口之,是否使用 ...

  9. TCP连接的关闭

    原文地址:http://lib.csdn.net/article/computernetworks/17264   TCP连接的关闭有两个方法close和shutdown,这篇文章将尽量精简的说明它们 ...

随机推荐

  1. Linux学习历程——Centos 7 chmod命令

    一.命令介绍 chmod 命令,是Linux管理员最常用的命令之一,用于修改文件或目录的访问权限. Linux系统中,每一个文件都有文件所有者和所属群组,并且规定文件的所有者,所属群组,以及其他人队问 ...

  2. Java基础系列--04_数组

    一维数组: (1)数组:存储同一种数据类型的多个元素的容器. (2)特点:每一个元素都有编号,从0开始,最大编号是数组的长度-1. 编号的专业叫法:索引 (3)定义格式 A:数据类型[] 数组名;(一 ...

  3. Ranger-Kafka插件安装

    Ranger-Kafka插件安装, 使用Ranger0.7.0版本,集成Kafka插件到Kafka集群, Kafka Plugin需要安装到所有的Kafka的集群节点上面. 1.登陆Kafka的安装用 ...

  4. 添加python虚拟环境

    在我centos上装有两个python版本 # 我在~/py3/目录下创建虚拟环境,该目录为python3的一个独立环境 [root@localhost /]# cd home [root@local ...

  5. css display和vertical-align 属性

    display 定义和用法 display 属性规定元素应该生成的框的类型. 实例 <html> <head> <style type="text/css&qu ...

  6. 19 款仿 Bootstrap 后台管理主题免费下载

    声明: 1. 本篇文章提到的仿 Bootstrap 风格的主题,是基于 jQuery 的 ASP.NET MVC 控件库的主题. 2. FineUIMvc(基础版)完全免费,可以用于商业项目. 目录 ...

  7. 【故障公告】10:30-10:45 左右 docker swarm 集群节点问题引发故障

    非常抱歉,今天 10:30-10:45 左右由于 docker swarm 集群节点出现问题,造成除博客之外的站点出现访问异常,由此给您带来很大的麻烦,请您谅解. 故障开始时出现有时访问正常有时访问出 ...

  8. Linux增加开放端口号

    Linux增加开放端口号 : 方法一:命令行方式 1. 开放端口命令: /sbin/iptables -I INPUT -p tcp --dport 80 -j ACCEPT   2.保存:/etc/ ...

  9. 美化ubuntu18.04,并安装搜狗输入法

    目录 美化Ubuntu 下载主题和图标文件 下载GNOME3 美化过程 安装输入法 下载并安装搜狗输入法 安装fcitx框架 安装过程 美化Ubuntu 下载主题和图标文件 下载地址:https:// ...

  10. C++通用WMI接口实现获取Windows操作系统内核版本号

    作为一名Windows开发者,能熟练掌握WMI技术,在开发Windows应用程序的时候往往能够事半功倍.今天来给大家分享一个使用WMI来获取Windows操作系统内核版本号的例子. 首先我们打开WMI ...