Linux 系统编程 学习:008-基于socket的网络编程3:基于 TCP 的通信
背景
上一讲我们介绍了 基于UDP 的通信
这一讲我们来看 TCP 通信。
知识
TCP(Transmission Control Protoco 传输控制协议)。
TCP是一种面向广域网的通信协议,目的是在跨越多个网络通信时,为两个通信端点之间提供一条具有下列特点的通信方式:
基于流的方式;
面向连接;
可靠通信方式;
在网络状况不佳的时候尽量降低系统由于重传带来的带宽开销;
通信连接维护是面向通信的两个端点的,而不考虑中间网段和节点。
为满足TCP协议的这些特点,TCP协议做了如下的规定:
- 数据分片:在发送端对用户数据进行分片,在接收端进行重组,由TCP确定分片的大小并控制分片和重组;
- 到达确认:接收端接收到分片数据时,根据分片数据序号向发送端发送一个确认;
- 超时重发:发送方在发送分片时启动超时定时器,如果在定时器超时之后没有收到相应的确认,重发分片;
- 滑动窗口:TCP连接每一方的接收缓冲空间大小都固定,接收端只允许另一端发送接收端缓冲区所能接纳的数据,TCP在滑动窗口的基础上提供流量控制,防止较快主机致使较慢主机的缓冲区溢出;
- 失序处理:作为IP数据报来传输的TCP分片到达时可能会失序,TCP将对收到的数据进行重新排序,将收到的数据以正确的顺序交给应用层;
- 重复处理:作为IP数据报来传输的TCP分片会发生重复,TCP的接收端必须丢弃重复的数据;
- 数据校验:TCP将保持它首部和数据的检验和,这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到分片的检验和有差错,TCP将丢弃这个分片,并不确认收到此报文段导致对端超时并重发。
sequenceDiagram
participant Server
participant Client
%% Note right of Client: Client主动连接->
Note right of Server: 双方都创建socket对象
Server ->> Server: socket
Client ->> Client: socket
Note left of Server: 服务器一般绑定端口号
Server ->> Server: bind
Note left of Server: 服务器监听是否有连接请求
Server ->> Server: listen
Note left of Client: 客户端请求链接
Client ->> Server: connect
Server ->> Server: accept
Note right of Server: 收发消息
Client -->> Server: send/recv
Server -->> Client: send/recv
Note right of Server: 关闭连接
Client --> Client: close
Server --> Server: close
有关函数介绍
根据流程图,我们知道,在UDP通信中,使用到了这些函数:socket()
、bind()
、sendto()
、recvfrom()
。
上面的函数我们在《基于UDP 的通信》 中已经讲过,这里不再重复了。
在TCP中,多了这几个函数:listen()
、connect()
、accept()
。
服务器调用listen
监听 客户端的 connect
;listen
成功时,服务器使用由accept
获取到的新的套接字进行通信。
当客户端调用connect函数时,将引发三次握手过程:客户端首先发送SYN请求分组,此时服务端会将请求放入SYN队列,同时向客户端发送ACK确认报文,然后客户端向服务端再次发送ACK报文。服务端收到ACK确认报文后,将SYN里的连接请求移入ACCEPT队列。此时三次握手结束,即TCP连接成功建立。然后内核通知用户空间的阻塞的服务进程,服务进程调用accept仅仅是从ACCEPT队列里取出一个连接而已。也就是说客户端调用connect连接服务器,与服务器调用accept“接受”连接是两个独立的过程。
listen
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int listen(int sockfd, int backlog);
描述: 将尚未建立连接的socket转换为被动socket,并监听发给这个被动socket的connect请求。
参数解析:
sockfd:由socket
函数成功返回的值
backlog :内核应该为相应套接口排队的最大连接个数(不是用来限制socket的最大连接数),一般为以下两个队列的大小之和,即未完成三次握手队列 + 已经完成三次握手队列。即:TCP模块允许的已完成三次握手过程(TCP模块完成)但还没来得及被应用程序accept的最大链接数。
内核为任何一个给定的监听套接口维护两个队列:
1、未完成连接队列(incomplete connection queue),每个这样的SYN分节对应其中一项:已由某个客户发出并到达服务器,而服务器正在等待完成相应的TCP三次握手过程。这些套接口处于SYN_RCVD状态。
2、已完成连接队列(completed connection queue),每个已完成TCP三次握手过程的客户对应其中一项。这些套接口处于ESTABLISHED状态。
当来自客户的SYN到达时,TCP在 未完成连接队列 中创建一个新项,然后响应以三次握手的第二个分节:服务器的SYN响应,其中稍带对客户SYN的ACK(即SYN+ACK)。这一项一直保留在未完成连接队列中,直到三路握手的第三个分节(客户对服务器SYN的ACK)到达或者该项超时为止(曾经源自Berkeley的实现为这些未完成连接的项设置的超时值为75秒)。如果三路握手正常完成,该项就从未完成连接队列移到已完成连接队列的队尾。当进程调用accept时,已完成连接队列中的队头项将返回给进程,或者如果该队列为空,那么进程将被投入睡眠,直到TCP在该队列中放入一项才唤醒它。
返回值: 成功返回0,失败返回-1,置errno:
- EADDRINUSE:另一个套接字已在同一端口上侦听。
- EADDRINUSE:(Internet域套接字)sockfd引用的套接字以前没有绑定到地址,在尝试将其绑定到临时端口时,确定临时端口范围中的所有端口号当前都在使用中。
- EBADF:参数sockfd不是有效的描述符。
- ENOTSOCK:文件描述符sockfd没有引用套接字。
- EOPNOTSUPP:套接字的类型不支持listen()操作。
主动socket和被动socket
一般来说,使用socket函数创建的socket默认是主动socket,这意味着一个主动的socket可以调用connect
跟一个被动socket建立一个连接,对主动socket来说,这叫主动打开。
被动socket是一个通过调用listen
函数监听要发起连接的socket,当被动socket接受一个连接通常称为被动打开。
在大多数网络程序中,服务端会作为被动socket被动接受连接,而客户端会作为主动socket主动发起连接。
服务端通过socket函数创建的socket是主动socket,而listen函数就是把这个还未接受连接的主动socket转换为被动socket,因为服务端只需要被动接受客户端的连接请求。
Linux系统设置未连接队列最大数限制
linux系统tcp/ip协议栈有个选项可以设置未连接队列大小限制tcp_max_syn_backlog
可以通过命令:cat /proc/sys/net/ipv4/tcp_max_syn_backlog
查看
Linux 系统中提供somaxconn
这个参数,它定义了系统中每一个端口最大的监听队列的长度,这是个全局的参数,默认值为128
可以通过命令: cat /proc/sys/net/core/somaxconn
查看
connect
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
描述: 连接一个被动socket
参数解析:
sockfd:主动socket
addr:目的地址
addrlen:地址属性的长度(addr的大小)
返回值:成功返回0,失败返回-1,置errno:
- EAFNOSUPPORT:传递的地址在其sau family字段中没有正确的地址系列。
- EAGAIN :路由缓存中的条目不足。
- EALREADY:套接字未阻塞,上一次连接尝试尚未完成。
EBADF:文件描述符不是描述符表中的有效索引。 - ECONNREFUSED:没有人监听远程地址。
- EFAULT :套接字结构地址在用户的地址空间之外。
- EINPROGRESS:套接字未阻塞,无法立即完成连接。可以通过选择要写入的套接字来选择(2)或轮询(2)以完成。
- EINTR :系统调用被捕获的信号中断。
- EISCONN:套接字已连接。
- ENETUNREACH:无法访问网络。
- ENOTSOCK:sockfd不是套接字。
- EPROTOTYPE:套接字类型不支持请求的通信协议。例如,在尝试将UNIX域数据报套接字连接到流套接字时,可能会发生此错误。
- ETIMEDOUT:尝试连接时超时。服务器可能太忙,无法接受新连接。请注意,对于IP套接字,当服务器上启用Syncookie时,超时可能非常长。
accept
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
#define _GNU_SOURCE /* See feature_test_macros(7) */
#include <sys/socket.h>
int accept4(int sockfd, struct sockaddr *addr,
socklen_t *addrlen, int flags);
描述: 从内核的ACCEPT队列中取出对应被动socket的连接,关于连接的有关属性填入addr中。
参数解析:
sockfd:对应的被动socket
addr:保存连接方的addr属性的容器
len:addr属性的长度
返回值: 成功返回可用于连接的新socket,失败返回-1,置errno:
此外,可能会返回新套接字的网络错误以及为协议定义的网络错误。各种Linux内核可以返回其他错误,例如ENOSR、ESOCKTNOSUPPORT、EPROTONOSUPPORT、ETIMEDOUT。在跟踪期间可以看到值ERESTARTSYS。
EMFILE :已达到打开的文件描述符数的每个进程限制
ENFILE :已达到系统范围内打开文件总数的限制
ENOBUFS, ENOMEM:没有足够的可用内存。这通常意味着内存分配受到套接字缓冲区限制,而不是系统内存的限制
ENOTSOCK sockfd不是套接字
EOPNOTSUPP 引用的套接字不是SOCK_STREAM类型
EPROTO :协议错误
EPERM (Linux) :防火墙规则禁止连接
例程
我们简单地进行一次TCP对答通信的实现
server.c
/*
# Copyright By Schips, All Rights Reserved
# https://gitee.com/schips/
#
# File Name: server.c
# Created : Sat 21 Mar 2020 04:43:39 PM CST
*/
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
typedef struct _info {
char name[10];
char text[54];
}info;
int main(int argc, char *argv[])
{
int my_socket;
unsigned int len;
int ret;
// 创建套接字
my_socket = socket(AF_INET, SOCK_STREAM, 0); // IPV4, TCP socket
if(my_socket == -1) { perror("Socket"); }
printf("Creat a socket :[%d]\n", my_socket);
// 用于接收消息
info buf ={0};
// 指定地址
struct sockaddr_in addr = {0};
addr.sin_family = AF_INET; // 地址协议族
addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //指定 IP地址
addr.sin_port = htons(12345); //指定端口号
// 服务器 绑定
bind(my_socket, (struct sockaddr *)&addr, sizeof(addr));
// my_socket 只用于监听
ret = listen(my_socket, 10);
if(-1 == ret) { perror("listen"); }
printf("Listening\n");
int new_socket;
struct sockaddr_in new = {0};
int new_addr_size;
// accept以后会返回一个新的套接字,用于与客户端通信
new_socket = accept(my_socket, (struct sockaddr*)&new, &new_addr_size);
printf("New socket is %d\n", new_socket);
perror("accept");
// 接收并打印消息
//recvfrom(my_socket, &buf, sizeof(buf), 0, NULL, NULL);
recv(new_socket, &buf, sizeof(buf), 0);
perror("recvfrom");
printf("%s: %s\n", buf.name, buf.text);
// 回复消息
sprintf(buf.name, "Server");
sprintf(buf.text, "Had recvied your message");
//sendto(my_socket, &buf, sizeof(buf), 0, NULL, NULL);
send(new_socket, &buf, sizeof(buf), 0);
perror("sendto");
// 关闭连接
//shutdown(my_socket, SHUT_RDWR); perror("shutdown");
close(new_socket); perror("close");
return close(my_socket); perror("close");
printf("%d\n", errno);
return errno;
}
client.c
/*
# Copyright By Schips, All Rights Reserved
# https://gitee.com/schips/
#
# File Name: client.c
# Created : Sat 21 Mar 2020 04:43:39 PM CST
*/
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
typedef struct _info {
char name[10];
char text[54];
}info;
int main(int argc, char *argv[])
{
int my_socket;
unsigned int len;
int ret;
// 创建套接字
my_socket = socket(AF_INET, SOCK_STREAM, 0); // IPV4, TCP socket
if(my_socket == -1) { perror("Socket"); }
printf("Creat a socket :[%d]\n", my_socket);
// 用于接收消息
info buf ={0};
// 指定地址
struct sockaddr_in addr = {0};
addr.sin_family = AF_INET; // 地址协议族
addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //指定 IP地址
addr.sin_port = htons(12345); //指定端口号
// 用于连接服务器
connect(my_socket, (struct sockaddr *)(&addr), sizeof(struct sockaddr_in));
if(-1 == ret) { perror("connect"); }
printf("connected\n");
// 回复消息
sprintf(buf.name, "Client");
sprintf(buf.text, "Hello tcp text.");
//sendto(my_socket, &buf, sizeof(buf), 0, NULL, NULL);
send(my_socket, &buf, sizeof(buf), 0);
perror("sendto");
// 接收并打印消息
//recvfrom(my_socket, &buf, sizeof(buf), 0, NULL, NULL);
recv(my_socket, &buf, sizeof(buf), 0);
perror("recvfrom");
printf("%s: %s\n", buf.name, buf.text);
// 关闭连接
//shutdown(my_socket, SHUT_RDWR); perror("shutdown");
return close(my_socket); perror("close");
printf("%d\n", errno);
return errno;
}
Linux 系统编程 学习:008-基于socket的网络编程3:基于 TCP 的通信的更多相关文章
- python核心编程学习(第三版)之网络编程
套接字 套接字是计算机网络数据结构.在任何类型的通信开始之前,网络应用程序必须创建套接字. 有两种类型的套接字,基于文件和面向网络的. unix套接字是第一个家族,AF_UNIX代表地址家族,缩写AF ...
- Linux 系统编程 学习:06-基于socket的网络编程1:有关概念
Linux 系统编程 学习:006-基于socket的网络编程1:有关概念 背景 上一讲 进程间通信:System V IPC(2)中,我们介绍了System IPC中关于信号量的概念,以及如何使用. ...
- Linux 系统编程 学习:07-基于socket的网络编程2:基于 UDP 的通信
Linux 系统编程 学习:07-基于socket的网络编程2:基于 UDP 的通信 背景 上一讲我们介绍了网络编程的一些概念.socket的网络编程的有关概念 这一讲我们来看UDP 通信. 知识 U ...
- 一点点linux系统的学习心得
我相信你正在阅读本文的时候,可能是因为你渴望学习Linux技术.我想分享一下过去两年中我自己的一些学习经历,希望你能更顺利地成为Linuxer. 两年前在Linux系统的运行和维护方面找到了一份工作( ...
- 安装虚拟机和Linux系统的学习
安装虚拟机和Linux系统的学习(随笔3) 1.安装虚拟机 首先我按着老师给的链接上的步骤一步一步安装VirtualBox,进行得十分顺利. 接着则是在虚拟机上安装Ubuntu. 然而安装完成以后按要 ...
- Java网络编程和NIO详解9:基于NIO的网络编程框架Netty
Java网络编程和NIO详解9:基于NIO的网络编程框架Netty 转自https://sylvanassun.github.io/2017/11/30/2017-11-30-netty_introd ...
- javaSE学习笔记(16)---网络编程
javaSE学习笔记(16)---网络编程 基本概念 如今,计算机已经成为人们学习.工作.生活必不可少的工具.我们利用计算机可以和亲朋好友网上聊天,也可以玩网游.发邮件等等,这些功能实现都离不开计算机 ...
- Python学习day34-面向对象和网络编程总结
figure:last-child { margin-bottom: 0.5rem; } #write ol, #write ul { position: relative; } img { max- ...
- centos Linux系统日常管理2 tcpdump,tshark,selinux,strings命令, iptables ,crontab,TCP,UDP,ICMP,FTP网络知识 第十五节课
centos Linux系统日常管理2 tcpdump,tshark,selinux,strings命令, iptables ,crontab,TCP,UDP,ICMP,FTP网络知识 第十五节课 ...
- 浅谈JAVA中如何利用socket进行网络编程(二)
转自:http://developer.51cto.com/art/201106/268386.htm Socket是网络上运行的两个程序间双向通讯的一端,它既可以接受请求,也可以发送请求,利用它可以 ...
随机推荐
- Spring Environment对象获取属性
String[] activeProfiles = env.getActiveProfiles();//获取当前是启用哪一个个配置文件 System.out.println(Arrays.toStri ...
- C#方法Extra
C#方法Extra 上次说的只是方法的一些基本东西,今天讲讲重载和 Lambda 表达式. 重载 方法的重载(overload)指的是同一个名字的方法,有着不一样的方法签名(method signat ...
- __declspec(dllexport)和__declspec(dllimport) (——declspec方法创建dll的方法已验证ok)
转载:https://www.cnblogs.com/chengbing2011/p/4084125.html __declspec(dllimport)和__declspec(dllexport)经 ...
- Matlab绘制子图subplot使用攻略
参考:https://jingyan.baidu.com/article/915fc414ad794b51394b20e1.html Matlab绘制子图subplot使用攻略 听语音 原创 | 浏览 ...
- 证明RSA算法在明文和公私钥中N不互质情况下仍然成立
关于RSA的基础过程介绍 下文中的 k 代表自然数常数,不同句子,公式中不一定代表同一个数 之前接触RSA,没有过多的思考证明过程,今天有感而发,推到了一遍 假设公钥 (e, N) , 私钥 (d, ...
- VueCroppie
下载 VueCroppie VueCroppie是一个Vue 2包装Croppie一个美丽的照片裁剪工具的Javascript由foliotek. 安装 NPM 安装vue-croppie-保存 CD ...
- Gearman实战第一弹:异步处理结算单
昨天梦回jm,醒来之后看着窗外万里晴空,想大声喊一句:爷青回! 我想起之前使用gearman的岁月.不知不觉也过了快5年,想总结一篇关于gearman的技术文章算是一种对青春的祭奠,再不写的话更少有p ...
- TP5 调用快递鸟api 查询快递信息
1,去快递鸟,下载sdk https://www.kdniao.com/api-track 下载PHPsdk 2,下载下来的事PHP文件,不是以类的形式显示的,所以为了方便,我把他封装成了类,不需要封 ...
- 多测师讲解html _段落标签002_高级讲师肖sir
<html> <head> <meta charset="UTF-8"> <title>段落标签</title> < ...
- element中过滤器filters的使用(开发小记)
之前在开发过程中遇到这么一个问题,一串数据需要在el-table中展示,其中含有金额字段,需要将其转换成标准数据格式,即三位一个逗号间隔. 今年刚毕业就上手项目了,第一次接触的Vue,开发经验少,也忘 ...