TCP/UDP(服务器、客户端源码)
[(12条消息) 网络编程(4.7作业)(TCP/UDP源代码)_m0_37565374的博客-CSDN博客]:
一. 套接字 socket
1.概念
- 最早的socket和消息队列、共享内存,管道一致只能实现一台主机中的多个进程间通信。后期加入了TCP/IP 使得socket支持不同主机的进程间通信。
- socket也是一个函数,返回值是一个文件描述符。
2. socket函数(创建套接字)
功能:在内核空间中创建两个缓冲区:接收缓冲区,发送缓冲,用户空间可以接收到两块空间的文件描述符
原型:
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
参数:
-
int domain:指定协议族(地址族)
Name Purpose Man page AF_UNIX, AF_LOCAL 局域网 unix(7) AF_INET ipv4 ip(7) AF_INET6 ipv6 ipv6(7) -
int type:
SOCK_STREAM 字节流式套接字:默认指定TCP协议 SOCK_DGRAM 数据报式套接字:默认指定UDP协议 SOCK_RAW 原始套接字,协议需要在第三个参数指定; -
int protocol:指定协议; 默认协议填0;
- IPPROTO_TCP tcp协议
- IPPROTO_UDP udp协议;
返回值:
- 成功,返回维护缓冲区的文件描述符,套接字文件描述符;
- 失败,返回-1,更新errno;
二. TCP
1. TCP模型图
2. TCP搭建相关函数
2.1 socket函数(创建套接字)(同上)
2.2 bind(绑定套接字)
功能:将IP和端口绑定到套接字上
原型:
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数:
-
int sockfd:指定要绑定到哪个套接字上,填对应文件描述符;
struct sockaddr*addr:通用地址信息结构体,真实的地址信息结构体根据地址族指定;
指定需要绑定到套接字上的IP和端口
--AF_INET--man 7 ip----
struct sockaddr_in {
sa_family_t sin_family; /* address family: AF_INET */ 必须填AF_INET;
in_port_t sin_port; /* port in network byte order */ 端口号的网络字节序,1024~49151
struct in_addr sin_addr; /* internet address */ 本机IP地址的网络字节序
终端输入:ifconfig查看本机IP
};
/* Internet address. */
struct in_addr {
uint32_t s_addr; /* address in network byte order */
};
- socklen_t addrlen:真实的地址信息结构体的大小:sizeof(struct sockaddr_in);
返回值:
- 成功,返回0;
- 失败,返回-1,更新errno;
2.3 listen(设置被动监听模式)
功能:将套接字设置为被动监听状态,监听是否有客户端连接成功;
原型:
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int listen(int sockfd, int backlog);
参数:
- int sockfd:指定将哪个套接字设置为被动监听状态; socket函数的返回值;
- int backlog:指定未完成连接队列的容量; 允许同时多少个客户端处于未完成连接状态。 一般填128
内核会维护两个队列:未完成连接的队列,已完成连接的队列;
返回值:
- 成功,返回0;
- 失败,返回-1,更新errno;
2.4 accept(获取客户信息)
功能:阻塞函数,从已完成连接的队列头中获取一个客户端信息,生成一个新的文件描述符
该文件描述符才是与客户端通信的文件描述符!!
原型:
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数:
-
int sockfd:被设置为被动监听状态的文件描述符;
-
struct sockaddr *addr:通用地址信息结构体,真实的地址信息结构体根据地址族指定;
存储连接成功的客户端的地址信息。如果不想获取,则填NULL; -
socklen_t *addrlen:真实的地址信息结构体的大小,注意是指针类型,需要在外部定义普通变量,赋值后取地址;
如果第二个参数填NULL,则当前参数填NULL;
返回值:
- 成功,返回新的文件描述符,该文件描述符用于与客户端通信;
- 失败,返回-1,更新errno;
2.5 recv(接收数据)
功能:从指定的套接字文件描述符中,接收数据
原型:
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
参数:
-
int sockfd:指定要从哪个文件描述符中获取数据,填accept函数获取到的新的文件描述符;
-
void *buf:该指针指向的内存空间中存储获取到的数据,可以是任意类型数据;
-
size_t len:指定要接收的数据大小,以字节为单位;
-
int flags:
0:阻塞方式接收,当没有数据的时候阻塞;
MSG_DONTWAIT:非阻塞;
返回值:
- >0 , 成功,返回成功接收到的字节数;
- =0, 对端关闭,返回0;
- =-1, 函数运行失败,更新errno;
2.6 send(发送消息)
功能:通过套接字文件描述符,向指定套接字发送数据;
原型:
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
参数:
-
int sockfd:指定要向哪个文件描述符发送数据,填accept函数获取到的新的文件描述符;
-
void *buf:指定要发送的数据首地址,可以是任意类型数据;
-
size_t len:指定要发送的数据大小,以字节为单位;
-
int flags:
0:阻塞方式发送,当数据满的时候阻塞;
MSG_DONTWAIT:非阻塞;
返回值:
- 成功,返回成功发送的字节数;
- 失败,返回-1,更新errno;
2.7 connect(连接服务器)
功能:通过服务器的IP和端口,连接指定服务器;
原型:
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数:
-
int sockfd:指定要将哪个套接字与服务器连接;
-
struct sockaddr *addr:通用地址信息结构体,真实的地址信息结构体根据地址族指定;
指定要连接的服务器所绑定的IP和端口; -
socklen_t addrlen:真实的地址信息结构体的大小:sizeof(struct sockaddr_in);
返回值:
- 成功,返回0;
- 失败,返回-1,更新errno;
二. UDP
1.UDP模型图
2.UDP搭建相关函数
2.1 socket(创建套接字)(同上)
2.2 bind(绑定套接字)(同上)
bind报错:
- bind: Address already in use ===>端口号被占用
- bind: Cannot assign requested address ===》IP地址错误,不是本机IP
2.3 recvfrom(接受数据)
功能:从指定套接字中接收数据,同时可以接收到该数据包从哪个发送方发送过来的
原型:
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
参数:
- int sockfd:指定要从哪个文件描述符中获取数据,填accept函数获取到的新的文件描述符;
- void *buf:该指针指向的内存空间中存储获取到的数据,可以是任意类型数据;
- size_t len:指定要接收的数据大小,以字节为单位;
- int flags:
0:阻塞方式接收,当没有数据的时候阻塞;
MSG _DONTWAIT:非阻塞;
- int flags:
- struct sockaddr *src_addr:通用地址信息结构体,该地址信息结构体中存储数据包是从谁那里发送过来的。
如果不想知道从谁那里过来,则填NULL; - socklen_t *addrlen:真实的地址信息结构体的大小,注意是指针类型,需要在外部定义普通变量,赋值后取地址;
如果上一个参数填NULL,则当前参数填NULL;
返回值:
- >0 , 成功,返回成功接收到的字节数;
- =0, 对端关闭,返回0; 只适用于TCP情况;
- =-1, 函数运行失败,更新errno;
recv(sockfd, buf, len, flags); 相当于 recvfrom(sockfd, buf, len, flags, NULL, NULL);
2.4 sendto(发送数据)
功能:将数据发送到指定的套接字中,需要指定发给谁,即指定好接收方的地址信息结构体
原型:
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
参数:
-
int sockfd:指定要向哪个文件描述符发送数据,填accept函数获取到的新的文件描述符;
-
void *buf:指定要发送的数据首地址,可以是任意类型数据;
-
size_t len:指定要发送的数据大小,以字节为单位;
-
int flags:
- 0:阻塞方式发送,当数据满的时候阻塞;
- MSG_DONTWAIT:非阻塞;
-
const struct sockaddr *dest_addr:通用地址信息结构体,真实的地址信息结构体跟着地址族来定
需要填充接收方的地址信息结构体,指定数据要发给谁; -
socklen_t addrlen:真实的地址信息结构体的大小:sizeof(struct sockaddr_in);
返回值:
- 成功,返回成功发送的字节数;
- 失败,返回-1,更新errno;
send(sockfd, buf, len, flags); 相当于sendto(sockfd, buf, len, flags, NULL, 0);
3. UDP中的connect函数(重点!)
-
udp中可以使用connect函数,但是不会产生连接
TCP中的connect函数会产生三次握手,将client和server连接。
UDP中的connect函数,仅仅是将对端的IP和端口记录到内核套接字空间中,此时是udp只能与记录的对端进行通信。
-
TCP中的connect函数只能被调用一次。
UDP中的connect函数可以被调用多次,刷新内核中对端的IP和端口,
如果想要清空内核中对端的地址信息,则将sin_family
成员设置为AF_UNSPEC,调用connect函数即可。
-
当udp采用connect函数的方式收发报文后,可以调用send write recv read函数。也可以使用sendto recvfrom.但是需要使用以下形式
sendto(sfd, buf, sizeof(buf), 0, NULL, 0); recvfrom(sfd, buf, sizeof(buf), 0, NULL, NULL);
udp中的connect函数的优点:
-
提升传输效率
-
- 不调用connect: 将对端的信息填充到内核 —> 发送报文 —>清空内核信息 —>将对端的信息填充到内核 —> 发送报文 —>清空内核信息—>…
- 调用connect: 将对端的信息填充到内核 —> 发送报文 —> 发送报文 —> 发送报文—> 发送报文
-
增加传输时候的稳定性
sendto(sfd, buf, sizeof(buf), 0, NULL, 0); recvfrom(sfd, buf, sizeof(buf), 0, NULL, NULL);
udp中的connect函数的优点:
-
提升传输效率
-
- 不调用connect: 将对端的信息填充到内核 —> 发送报文 —>清空内核信息 —>将对端的信息填充到内核 —> 发送报文 —>清空内核信息—>…
- 调用connect: 将对端的信息填充到内核 —> 发送报文 —> 发送报文 —> 发送报文—> 发送报文
-
增加传输时候的稳定性
-
- 可以防止AB进程在做大量数据传输过程中,接收到C进程的数据,造成数据错误。