五十五、linux 编程——TCP 连接和关闭过程及服务器的并发处理
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 连接和关闭过程及服务器的并发处理的更多相关文章
- 五十四 网络编程 TCP编程
Socket是网络编程的一个抽象概念.通常我们用一个Socket表示“打开了一个网络链接”,而打开一个Socket需要知道目标计算机的IP地址和端口号,再指定协议类型即可. 客户端 大多数连接都是可靠 ...
- 五十三、linux 编程——TCP 编程基本介绍
53.1 socket 套接字 53.1.1 介绍 Socket(套接字)是一种通讯机制,它包含一整套的调用接口和数据结构的定义,它给应用进程提供了使用如 TCP/UDP 灯网络协议进行网络通讯的手段 ...
- 孤荷凌寒自学python第五十五天初识MongoDb数据库
孤荷凌寒自学python第五十五天第一天初识MongoDb数据库 (完整学习过程屏幕记录视频地址在文末) 大家好,2019年新年快乐! 本来我想的是借新年第一天开始,正式尝试学习爬虫,结果今天偶然发现 ...
- 第三百五十五天 how can I 坚持
快一年了,三百五十五天了,等写个程序算算时间,看看日期和天数能不能对的上,哈哈. 计划还是未制定,天气预报还是没有写完,立马行动,发完这个博客,立马行动. 计划:设计模式1个月,三大框架3个月,计算机 ...
- 第三百五十五节,Python分布式爬虫打造搜索引擎Scrapy精讲—scrapy信号详解
第三百五十五节,Python分布式爬虫打造搜索引擎Scrapy精讲—scrapy信号详解 信号一般使用信号分发器dispatcher.connect(),来设置信号,和信号触发函数,当捕获到信号时执行 ...
- “全栈2019”Java第五十五章:方法的静态绑定与动态绑定
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...
- 第13章 TCP编程(2)_TCP的连接和关闭过程
4. TCP的连接和关闭过程 4.1 TCP连接的三次握手和四次挥手 (1)三次握手 ①第1次握手:建立连接.客户端发送连接请求报文段(SYN=1,sequence Number=x):然后客户端进入 ...
- python网络编程--TCP连接的三次握手(三报文握手)与四次挥手
一.TCP连接 运输连接有三个阶段: 连接建立.数据传送和连接释放. 在TCP连接建立过程中要解决以下三个问题: 1,要使每一方能够确知对方的存在. 2.要允许双方协商一些参数(如最大窗口之,是否使用 ...
- TCP连接的关闭
原文地址:http://lib.csdn.net/article/computernetworks/17264 TCP连接的关闭有两个方法close和shutdown,这篇文章将尽量精简的说明它们 ...
随机推荐
- eslint 代码缩进 报错及解决
一.背景 使用vue在VScode中正常写的代码,报了一堆的错误,仔细检查,发现都是缩进要么多了要么少了,总之是代码不规范的的报错. 二.原因 百度查了发现代码规范默认缩进2个空格,而VScode默认 ...
- [LeetCode] 23. 合并K个排序链表
题目链接: https://leetcode-cn.com/problems/merge-k-sorted-lists/ 题目描述: 合并 k 个排序链表,返回合并后的排序链表.请分析和描述算法的复杂 ...
- Thymeleaf入门(一)——入门与基本概述
一.概述 1.是什么 简单说, Thymeleaf 是一个跟 Velocity.FreeMarker 类似的模板引擎,它可以完全替代 JSP . 2.feature 1.Thymeleaf 在有网络和 ...
- random使用方法
random.random() 没有参数,选择0到1之间的随机浮点数 random.uniform(a, b) 生成指定范围内的随机浮点数如果a.b哪个大那个小都没关系,生成的都是在小的与大的之间的随 ...
- Spring Boot – Jetty配置
前言 默认情况下,Spring Boot会使用内置的tomcat容器去运行应用程序,但偶尔我们也会考虑使用Jetty去替代Tomcat:对于Tomcat和Jetty,Spring Boot分别提供了对 ...
- KVM宿主机上虚拟机动态添加新磁盘
(1)KVM宿主机查看运行的虚拟机 $ virsh list --all (2)将qcow2的磁盘移动到/var/lib/libvirt/images/,比如为centos.qcow2 (3)进入/e ...
- 播放器授权后播放内容时出现Cnario logo水印
问题描述 Player获取License后, 通过Messeenger发布到Player的内容前面出现Cnario 的logo水印, 如下图样式: 原因 出现这种情况一般是由于License授权不正确 ...
- Centos7 启动指定docker容器报错
今天做docker实验时,把docker镜像pull下后,启动报如下错误: 错误信息:WARNING: IPv4 forwarding is disabled. Networking will not ...
- Android艺术——性能优化问题
这次分析方向,我们主要包括:布局优化.绘制优化.内存泄漏优化.响应速度优化.ListView优化.Bitmap优化.线程优化. 布局优化:尽量的减少布局的层级,这意味着Android绘制时的工作量会变 ...
- 从输入URL到页面加载的全过程
前面的话 本文将详细介绍从输入URL到页面加载的全过程 概述 从输入URL到页面加载的主干流程如下: 1.浏览器构建HTTP Request请求 2.网络传输 3.服务器构建HTTP Response ...