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. 【转】网页禁止后退键BackSpace的JavaScript实现(兼容IE、Chrome、Firefox、Opera)

    var forbidBackSpace = function (e) { // 获取event对象 var ev = e || window.event; // 获取事件源 var obj = ev. ...

  2. fastjson SerializerFeature详解

  3. java拦截器(interceptor)

    1.声明式 (1)注解,使用Aspect的@Aspect (2)实现HandlerInterceptor /** * 拦截请求 * * @author Administrator * */ @Comp ...

  4. linux系统mysql-5.7 修改字符集

    起因:我在网上看修改mysql字符的文章时,都说配置/etc/mysql/my.cnf文件 然而我打开我上述的my.cnf文件时,发现里面的内容跟别人的不一样,我就觉得这个肯定不是正确的文件 经过我在 ...

  5. Python:time模块/random模块/os模块/sys模块

    time 模块 #常用方法 1.time.sleep(secs) (线程)推迟指定的时间运行.单位为秒. 2.time.time() 获取当前时间戳 python中时间日期格式化符号: %y 两位数的 ...

  6. ASP.NET Core 使用 Google 验证码(Google reCAPTCHA)

    关心最多的问题,不FQ能不能用,答案是能.Google官方提供额外的域名来提供服务,国内可以正常使用. 一. 前言 验证码在我们实际的生活场景中非常常见,可以防止恶意破解密码.刷票.论坛灌水.刷注册等 ...

  7. Git使用注意事项

    第一次用git时push时,突然想到我没有设置ssh key,却也可以push代码到自己仓库,那我本地登陆的账号Git是存在哪儿了呢? Git本地账户凭证管理 在第一次push到远程仓库时,git会提 ...

  8. spring boot中配置日志log和热部署

    Java的日志有很多 个人强烈不推荐log4j ,推荐log4j2和logback 在高并发,多线程的环境下log4j1 的性能和log4j2相比可以用junk来形容  对就是junk.log4j2的 ...

  9. Docker 核心技术之Dockerfile

    Dockerfile 简介 什么是Dockerfile Dockerfile其实就是根据特定的语法格式撰写出来的一个普通的文本文件 利用docker build命令依次执行在Dockerfile中定义 ...

  10. POJ 2411 Mondriaan's Dream -- 状压DP

    题目:Mondriaan's Dream 链接:http://poj.org/problem?id=2411 题意:用 1*2 的瓷砖去填 n*m 的地板,问有多少种填法. 思路: 很久很久以前便做过 ...