参考书:《Linux网络编程(第2版)》,宋敬彬等编著。
1.TCP通信流程
tcp连接过程图片来源:https://blog.csdn.net/u012234115/article/details/54142273
2. 套接字基础知识
通用套接字数据结构
struct sockaddr{ //套接字地址结构
sa_family_t sa_family, //协议族,类型为usigned_short
char sa_data[14] //协议族数据
}
实际使用的套接字数据结构
struct sockaddr_in { //以太网套接字地址结构
u8 sin_len, //本结构体的长度
u8 sin_family, //协议族,通常与socket的domain相同
u16 sin_port, //16位的端口号,网络字节序
struct in_addr sin_addr, //IP地址,32位
char sin_zero[8] //未用
}
struct in_addr{ //IP地址结构
u32 s_addr //32位IP地址,网络字节序
}
sockaddr_in.sin_family一般取值AF_INET表示TCP/IP协议。
sockaddr与sockaddr_in的大小完全一致,因此通常使用sockaddr_in进行设置,然后强制转换为sockaddr。
3. 创建套接字函数 socket()
socke()函数建立一个套接字文件描述符,调用成功返回文件描述符,失败返回-1.
#include <sys/types.h>
#include <sys/socket.h>
int socket(
int domain, //设置网络通信域,根据此参数选择协议族
int type, //设置套接字类型
int protocol //用于指定某个协议的特定类型
)
domain
名称 | 含义 | 名称 | 含义 |
---|---|---|---|
PF_UNIX,PF_LOCAL | 本地通信 | PF_X25 | ITU-T X.25/ISO-8208协议 |
PF_INET,AF_INET | IPv4 Internet协议 | PF_AX25 | amateur radio AX.25协议 |
PF_INET6 | IPv6 Internet协议 | PF_ATMPVC | 原始ATM PVC访问 |
PF_IPX | IPX-Novell协议 | PF_APPLETALK | appletalk |
PF_NETLINK | 内核用户界面设备 | PF_PACKET | 底层包访问 |
type
名称 | 含义 |
---|---|
SOCK_STREAM | Tcp连接,支持带外数据传输 |
SOCK_DGRAM | 支持udp连接 |
SOCK_SEQPACKET | 序列化包 |
SOCK_RAW | Raw类型,提供原始网络协议访问 |
SOCK_RDM | 提供可靠的数据报文,不过可能数据会有乱序 |
SOCK_PACKET | 专用类型,不能在通用程序中使用 |
SOCK_STREAM的套接字表示双向的字节流,与管道类似,流式套接字在进行通信之前必须已经使用connect()建立连接。连接中可以使用read() 和write()进行数据传输。流式通信保证数据不会丢失或重复接受,当数据在一定时间内仍没有接受完毕,可认为这个连接已经死掉。
protocol
用于指定某个协议的特定类型,通常协议中只有一种类型,protocol取值为0.
errno
值 | 含义 |
---|---|
EACCES | 没有权限建立制定的domain的type的socket |
EAFANOSUPPORT | 不支持所给的地址类型 |
EINVAL | 不支持此协议或者协议不可用 |
EMFILE | 进程文件表溢出 |
ENFILE | 已经达到系统允许打开的文件数量,打开文件过多 |
ENOBUFS/ENOMEM | 内存不足。socket只有到资源足够或者有进程释放内存 |
EPROTONOSUPPORT | 制定的协议type在domain中不存在 |
其他 |
4. 绑定地址端口 bind()
bind()函数将套接字和指定的端口进行绑定,如果使用connect()函数则没有绑定的必要。绑定成功返回0,失败返回-1.
#include <sys/types.h>
#include <sys/socket.h>
int bind(
int sockfd, //文件描述符
const struct sockaddr *my_addr, //sockaddr结构体指针
socket_t addrlen //sockaddr结构体的长度
)
errno
值 | 含义 | 备注 |
---|---|---|
EADDRINUSE | 给定地址已用 | |
EBADF | sockfd不合法 | |
EINVAL | sockfd已经绑定到其他地址 | |
ENOTSOCKET | sockfd不是socket描述符 | |
EACCESS | 地址被保护,用户权限不足 | |
待补充 |
5. 监听本地端口 listen()
服务器顺序处理客户端连接,同一时间只能处理一个客户端连接。当多个客户端连接请求同时到来时,将不能处理的客户端连接请求放到等待队列中,队列长度由listen()定义。
listen()函数仅对SOCK_STREAM和SOCK_SEQPACKET的协议有效。
设置成功返回0,失败返回-1.
#include <sys/socket.h>
int listen(
int sockfd, //侦听的描述符
int backlog //队列最大长度
)
errno
值 | 含义 |
---|---|
EADDRINUSE | 另一个socket已经在同一端口侦听 |
EBADF | sockfd不合法 |
ENOTSOCK | sockfd不是socket的描述符 |
EOPNOTSUPP | socket不支持listen操作 |
6. 接收一个网络请求 accept()
当一个客户端的请求到达服务器时,会在队列中等待,直到服务器处理请求。
accept()函数执行成功,返回表示客户端连接的socket描述符,失败返回-1.
#include <sys/types.h>
#include <sys/socket.h>
int accept(
int sockfd, //监听的文件描述符
struct sockaddr *addr, // 存储客户端信息的结构体
socket_len *addrlen //上结构体的长度
)
accept()函数调用成功后,会将客户端的信息存储在*addr指向的结构体中。
errno
值 | 含义 |
---|---|
待补充 |
7. 连接目标服务器 connect()
客户端建立套接字之后,不需要进行绑定就可以使用connect()函数直接连接服务器。connect()成功返回0,失败返回-1.
#include <sys/types.h>
#include <sys/socket.h>
int connect(
int sockfd, //需要与服务器连接的文件描述符
struct sockaddr *addr, //包含服务器信息的结构体
int addrlen // 上述结构体的长度
)
errno
值 | 含义 |
---|---|
待补充 |
8. 写入函数 write() & send()
使用write()对套接字写入的方式与对普通文件的操作一样。
ssize_t write(
int fd, //文件描述符
const void *buf, //数据缓存区
size_t nbytes//写入字符的大小
);
返回值大于0:返回写入数据的大小,并不一定写入了指定的全部数据,所以一般放在while循环里。
返回值小于0:EINTR表示在写的时候出现了中断错误。EPIPE表示网络连接出现问题。
send()函数功能与write()类似,提供了第四个函数控制发送操作。
int send(
int sockfd,//文件描述符
void *buf,//数据缓存区
int len,//数据大小
int flags
);
flags值 | 含义 |
---|---|
MSG_DONTROUTE | send函数使用的标志,表示目的主机在本地网络上,不需要查找表 |
MSG_OOB | 可以接受和发送带外的数据 |
9. 读取函数 read() & recv()
read()从套接字中读取数据。
ssize_t read(
int fd,//文件描述符
void *buf,//数据缓存区
int length//要读的数据大小
);
返回值等于0:已经读到文件结尾。
返回值大于0:实际读的数据大小。
返回值小于0:与write相同。
recv()功能与read()类似,添加了第四个参数控制接受操作。
int recv(
int sockfd,//文件描述符
void *buf,//数据缓存区
int len,//数据大小
int flags
);
flags值 | 含义 |
---|---|
MSG_OOB | 可以接受和发送带外的数据 |
MSG_PEEK | recv使用的标志,只从缓冲区读取数据而清除其内容,下次读时仍然是一样的内容,一般用在多进程读写数据时 |
MSG_WAITALL | recv使用的标志,表示等到所有信息都到达时才返回。当1.读到指定数据大小,返回len2.读到文件结尾返回值小于等于len3.操作发生错误时返回-1 |
10. 关闭套接字 close() & shutdown()
关闭连接可以用close()实现,关闭之后就不再使用此描述符进行数据收发。
#include <sys/socket.h>
int close( int sockfd)
shutdown()函数可以使用更多方式关闭连接,调用成功返回0,失败返回-1。
#include <sys/socket.h>
int shutdown(
int sockfd, // 要关闭的描述符
int how //关闭方式
)
how
值 | 含义 |
---|---|
SHUT_RD | 0,切断读,不能通过此方式进行读操作 |
SHUT_WR | 1,切断写,不能通过此方式进行写操作 |
SHUT_RDWR | 2,切断读写,与close相同 |
网友评论