一、基于TCP传输层的编程模型
TCP是面向连接的,安全可靠的。
三次握手
服务器端编程模型
1、创建一个用于网络通讯的设备 通讯端点
socket(2)
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
功能:创建一个用于通讯的端点
参数:
domain:
AF_INET:应用于IPV4地址家族的
AF_INET6:应用于IPV6地址家族的
type:
SOCK_STREAM:可靠的、基于连接的 双向的、队列式的 TCP
SOCK_DGRAM:支持数据包 不可靠的 无连接的 UDP
protocol:
0
返回值:
-1 错误 errno被设置
返回一个新的文件描述符
2、将这个通讯端点和本机的ip地址、端口号做绑定
bind(2)
int bind(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
功能:绑定名字到地址。创建socket以后,socket中有地址空间但是没有具体地址放在这个地址空间。需要将具体的地址和socket的地址空间绑定。
参数:
sockfd:已经创建好的socket,但是这个socked没有具体的地址
addr:指定了具体的地址,将这个地址绑定到sockfd中
addrlen:指定了addr的大小、字节数
返回值:
0 成功
-1 错误 errno被设置
地址家族的通用结构
struct sockaddr{
sa_family_t sa_family;
char sa_data[14];
};
ipv4和ipv6
man in.h
#include <netinet/in.h>
in_port_t uint16_t
in_addr_t uint32_t
sa_family_t <sys/socket.h>
struct in_addr{
in_addr_t s_addr;
};
ipv4的具体地址
struct sockaddr_in{
sa_family_t sin_family; //AF_INET.
in_port_t sin_port; //Port number.
struct in_addr sin_addr; //IP address.
};
INADDR_ANY IPv4 local host address
这是一个宏,宏的本质是一个整数。代表了本机的所有的地址。
端口号 0~65535
但是5000以下最好不要用。因为已经被国际组织使用了。
sin_port 采用网络字节序
需要将本机字节序的数字转换为网络字节序
系统提供了函数。处理本机字节序和网络字节序的问题
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
h host n net
s short l long to
ip地址 字符串格式 二进制格式 互相转换
inet_pton(3)
#include <arpa/inet.h>
int inet_pton(int af, const char *src, void *dst);
功能:从字符串格式转换为二进制格式 IPV4 IPV6
参数:
af:
AF_INET or AF_INET6
src:字符串格式的ip地址
dst:存储了网络地址结构的信息
返回值:
1 成功
0 src无效
-1 错误 errno被设置
struct in_addr
#include <arpa/inet.h>
const char *inet_ntop(int af, const void *src,
char *dst, socklen_t size);
功能:二进制到字符串的转化
参数:
af:
AF_INET or AF_INET6
src: struct in_addr
dst:用来存储字符串的空间
size:指定了空间的有效字节数
返回值:
NULL 错误 errno被设置
非空 返回dst的地址,字符串的首地址。
ip地址 127.0.0.1 本地地址 环回地址
测试机器的网络设备工作是否正常。
setsockopt(2)
3、在这个通讯端点上监听客户端连接的到来,如果有连接的到来,将到达的连接存放在缓冲区中(队列的数据结构)
listen(2)
int listen(int sockfd, int backlog);
功能:在socket监听连接。将sockfd标记为被动连接。接收即将到来的客户端请求。
参数:
sockfd:指定了被监听的socket
backlog:指定了未决连接的最大数。
返回值:
0 成功
-1 错误 errno被设置
4、从这个缓冲区队列中取出一个客户端连接,返回一个连接描述符用于和客户端的通讯。(这个连接描述符称为conn_fd)
accept(2)
int accept(int sockfd, struct sockaddr *addr, \
socklen_t *addrlen);
功能:在socket上接收一个连接
参数:
sockfd:指定了监听的socket
addr:在这个地址空间里填充了客户端的地址和端口号
addrlen:空间里指定了addr的长度。如果addr为NULL,那么也要设置为NULL
返回值:
-1 错误 errno被设置
返回一个非负的整数,就是连接描述符
5、使用conn_fd和客户端通讯
(1)获取客户端的请求
read(2)
(2)处理客户端请求
(3)响应客户端
write(2)
6、关闭conn_fd,终止和客户端的通讯
close(conn_fd);
客户端的编程模型
1、创建一个用于通讯的设备(通讯端点)
socket(2)
2、使用这个端点连接到服务器(IP地址和端口号)
connect(2)
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
功能:在socket上发起一个连接
参数:
sockfd:指定socket,将这个socket连接到addr的地址空间
addr:指定具体的地址空间,要连接到的地址空间。server
addrlen:指定了addr的长度
返回值:
0 成功
-1 错误 errno被设置
3、向服务器发送消息
4、等待服务器端的响应消息
5、处理服务器的响应消息
6、关闭设备,结束本次通讯。
举例说明 编写基于TCP的通讯模型
服务器端代码 参见 server.c
客户端代码参见 client.c
172.30.3.93
二、并发服务器的实现
三种方法实现服务器的并发 多进程 多线程 多路复用
使用多进程实现服务器的并发功能
什么时候?什么地方?子进程才登场。
fork(2)
子进程负责的任务
子进程负责和客户端的通讯
关闭s_fd。
信息处理
关闭和客户端的连接
父进程负责的任务
父进程负责从未决连接队列中取出一个连接,创建和客户端通讯的文件描述符
创建子进程
关闭和客户端的连接
负责对子进程收尸。非阻塞收尸