C++ advance(六)Linux 高并发网络编程开发 --2.tcp 三次握手 - 并发
回顾:socket tcp server 实现
1. 创建套接字
1 | int lfd = socket (); |
2. 绑定本地 IP 和端口
1 | struct sockaddr_in serv; |
3. 监听
1 | listen (lfd, 128); |
4. 等待并接收连接请求
1 | struct sockaddr_in client; |
5. 通信
接收数据:read/recv
发送数据:write/send
6. 关闭
close (lfd);
close (cfd);
回顾:socket tcp client 实现
1. 创建套接字
1 | int fd = socket; |
2. 连接服务器
1 | // 客户端的端口可以不用固定,直接占用一个空闲端口就可以了(随机分配) |
3. 通信
接收数据:read/recv
发送数据:write/send
4. 断开连接
1 | close (fd); |
一、TCP 客户端编程
1.socket () 函数
1 | int socket (int domain, int type, int protocol); |
(1) domain:即协议域,又称为协议族(family), 地址族。
常用的协议族有,AF_INET、AF_INET6、AF_LOCAL(或称 AF_UNIX,Unix 域 socket)、AF_ROUTE 等等。协议族决定了 socket 的地址类型,在通信中必须采用对应的地址,如 AF_INET 决定了要用 ipv4 地址(32 位的)与端口号(16 位的)的组合、AF_UNIX 决定了要用一个绝对路径名作为地址。
(2) type:指定 socket 类型。
常用的 socket 类型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET 等等(socket 的类型有哪些?)。这个参数指定一个套接口的类型,套接口可能的类型有:SOCK_STREAM、SOCK_DGRAM、SOCK_SEQPACKET、SOCK_RAW 等等,它们分别表明字节流、数据报、有序分组、原始套接口。这实际上是指定内核为我们提供的服务抽象,比如我们要一个字节流。需要注意的,并不是每一种协议簇都支持这里的所有的类型,所以类型与协议簇要匹配。
(3) protocol:故名思意,就是指定协议。
常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC 等,它们分别对应 TCP 传输协议、UDP 传输协议、STCP 传输协议、TIPC 传输协议。详见 usr/include/linux/in.h。
当 protocol 为 0 时,会自动选择 type 类型对应的默认协议。
(4) 返回值
当我们调用 socket 创建一个 socket 时,返回的 socket 描述字它存在于协议族(address family,AF_XXX)空间中,但没有一个具体的地址。如果想要给它赋值一个地址,就必须调用 bind () 函数,否则就当调用 connect ()、listen () 时系统会自动随机分配一个端口。
2.memset () 函数
string.h 的函数 memset () 将某一内存块的前 n 个字元全部设定为某一资字元。1
2
3
4
5
6
7
8
9
int main (void)
{
char s [];
printf ("% s\n", memset (s, 'n', 13));
return 0;
}
3.htons () 函数
4.inet_pton () 函数
5.bind () 函数
1 | int bind (int sockfd, struct sockaddr *my_addr, socklen_t addrlen); |
当一个套接字被创建后,存在一个名字空间(地址族),但它没有被命名。bind () 将套接字地址(包括本地主机地址和本地端口地址)与所创建的套接字号联系起来,即将名字赋予套接字,以对 socket 定位。
bind () 用来设置给参数 sockfd 的 socket 一个名称。此名称由参数 my_addr 指向一 sockaddr 结构,对于不同的 socket domain 定义了一个通用的数据结构。1
2
3
4struct sockaddr {
unsigned short int sa_family;
char sa_data [14];
};
sa_data 最多使用 14 个字符长度。
此 sockaddr 结构会因使用不同的 socket domain 而有不同结构定义,例如使用 AF_INET domain,其 socketaddr 结构定义便为:1
2
3
4
5
6struct socketaddr_in {
unsigned short int sin_family;
uint16_t sin_port;
struct in_addr sin_addr;
unsigned char sin_zero [8];
};1
2
3struct in_addr {
uint32_t s_addr;
};
sin_port 为使用的 port 编号
sin_addr.s_addr 为 IP 地址
sin_zero 未使用,是为了让 sockaddr 与 sockaddr_in 两个数据结构保持大小相同而保留的空字节。
6.connect () 函数
1 | int connect (int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen); |
connect () 用来将参数 sockfd 的 socket 连至参数 serv_addr 指定的网络地址。参数 addrlen 为 sockaddr 的结构长度。
返回值:成功则返回 0,失败返回 - 1,错误原因存于 errno 中。
7.fgets () 函数
虽然用 gets () 时有空格也可以直接输入,但是 gets () 有一个非常大的缺陷,即它不检查预留存储区是否能够容纳实际输入的数据,换句话说,如果输入的字符数目大于数组的长度,gets 无法检测到这个问题,就会发生内存越界,所以编程时建议使用 fgets ()。
fgets () 的原型为:1
2
char *fgets (char *s, int size, FILE *stream);
fgets () 虽然比 gets () 安全,但安全是要付出代价的,代价就是它的使用比 gets () 要麻烦一点,有三个参数。它的功能是从 stream 流中读取 size 个字符存储到字符指针变量 s 所指向的内存空间。它的返回值是一个指针,指向字符串中第一个字符的地址。
8.write () 函数
1 |
|
9.read () 函数
实现
1 |
|
二、TCP 服务器端编程
三、TCP 三次握手
开启 socket 的时候,操作系统会自动握手;关闭 socket 的时候,操作系统会自动挥手。所以这两个概念只要了解就行了。
三、TCP 四次挥手
四、滑动窗口
五、错误处理函数封装
如果一个函数有部分不符合你的需求,可以自己封装,例如:淘宝封装 nginx
六、TCP 多进程并发服务器
进程和线程的数据共享模式不一样。
七、TCP 多线程并发服务器
C++ advance 文章总览
C++ advance(四)Linux 命令基础 —1.Linux 常用命令
C++ advance(四)Linux 命令基础 —2.vim 和 gcc 和 library
C++ advance(四)Linux 命令基础 —3.makefile 和 gdb 和 IO
C++ advance(四)Linux 命令基础 —4.stat 和 readdir 和 dup2
C++ advance(五)Linux 进程和线程 —1. 进程控制
C++ advance(五)Linux 进程和线程 —2. 进程间通信
C++ advance(五)Linux 进程和线程 —3. 信号
C++ advance(五)Linux 进程和线程 —4. 进程和线程
C++ advance(五)Linux 进程和线程 —5. 线程同步
C++ advance(六)Linux 高并发网络编程开发 —1. 网络编程基础 socket
C++ advance(六)Linux 高并发网络编程开发 —2.tcp 三次握手 - 并发
C++ advance(六)Linux 高并发网络编程开发 —3.tcp 状态转换 - selcet poll
C++ advance(六)Linux 高并发网络编程开发 —4.epoll udp
C++ advance(六)Linux 高并发网络编程开发 —5. 广播 - 组播 - 本地套接字
C++ advance(六)Linux 高并发网络编程开发 —6.libevent
C++ advance(六)Linux 高并发网络编程开发 —7.xml json
C++ advance(七)Linux 高并发 web 服务器开发 —1.
C++ advance(七)Linux 高并发 web 服务器开发 —2.
C++ advance(七)Linux 高并发 web 服务器开发 —3.