1、TCP服务器编写流程
头文件:
#include <sys/socket.h>
1.1 创建套接字
函数原型:
int socket(int domain, int type, int protocol);
参数:
domain: 网域
AF_INET : IPv4
AF_INET6 : IPv6
AF_UNIX : 本地通讯type:选择传输协议 tcp/udp
SOCK_STREAM ; tcp
SOCK_DGRAM : udpprotocol: 基本废弃, 一般赋 0
返回值: 成功返回描述网络套接字 sockfd, 失败返回-1
举例: 创建描述网络的套接字:
int sfd = socket(AF_INET,SOCK_STREAM,0);
1.2 绑定
头文件:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
作用:绑定一个端口和IP地址,使套接口与指定的端口号和IP地址相关联
函数原型:
int bind(int sockfd, const struct sockaddr *my_addr, socklen_t addrlen);
参数:
sockfd 为前面 socket 的返回值, 描述网络的套接字
my_addr:封装 ip 地址和端口号
struct sockaddr //一般很少用
{
unsigned short int sa_family; // AF_INET。
char sa_data[14]; //IP 和端口
};
struct sockaddr_in //常用的结构体
{
unsigned short int sin_family; //AF_INE
uint16_t sin_port; //使用的 port 编号
struct in_addr sin_addr; // IP 地址
unsigned char sin_zero[8]; //未使用
};
端口:10000-65535之间随意选
转化:
uint32_t htonl(uint32_t hostlong);//本函数将一个 32 位数从主机字节顺序转换成无符号长整型网络字节顺序
uint16_t htons(uint16_t hostshort);//将一个无符号短整型的主机数值转换为网络字节顺序
uint32_t ntohl(uint32_t netlong);//将一个无符号长整形数从网络字节顺序转换为主机字节顺序。
uint16_t ntohs(uint16_t netshort);//将一个 16 位数由网络字节顺序转换为主机字节顺序。
IP的填写方式:
struct in_addr
{
uint32_t s_addr; //=inet_addr("192.168.1.22");
};
返回值:成功则返回0,失败返回-1
举例:
#define PORT 33333
#define IP "192.168.110.123"
struct sockaddr_in ser_addr;
ser_addr.sin_family = AF_INET;
ser_addr.sin_port = htons(PORT);
ser_addr.sin_addr.s_addr = inet_addr(IP);
inet_aton(“192.168.1.22”,&ser_addr.sin_addr);
bind(sfd,(struct sockaddr)&ser_addr,sizeof(ser_addr));
1.3 监听
作用:设置允许的最大连接数(瞬间处理的阀值),listen函数。
使用服务器的这个端口和IP处于监听状态,等待网络中某一客户机的连接请求。如果客户端哟连接请求,端口就会接受这个链接。
函数原型:
int listen(int sockfd, int backlog);
参数:
sockfd 为前面 socket 的返回值, sfd
backlog 指定同时能处理的最大连接要求, 通常为 10 或者 5。 最大值可设至 128( 不是最多可以连接 128个客户端, 是一个瞬时处理的阈值)
返回值: 成功则返回 0, 失败返回-1
1.4 等待客户端连接
函数原型:
int accept(int sockfd, struct sockaddr *addr,socklen_t *addrlen);
参数:
sockfd 为前面 socket 的返回值, 即 sfd
addr: 提供空间, 用于接受客户端的 ip 地址和端口号
addrlen: 第二个参数大小返回值:
返回新的套接字描述符, 专门用于与建立的客户端通信
失败-1;
举例:
struct sockaddr_in cli_addr = {0};
socklen_t len = sizeof(cli_addr);
int cfd = accept(sfd,(struct sockaddr *)&cli_addr,&len);
1.5 读写函数
可以使用read和write函数进行。
写函数
函数原型;
ssize_t send(int s, const void *buf, size_t len, int flags);
参数:
s: 通信套接字
buf: 要发送的数据缓冲区
len: 数据长度
flags: 一般赋 0 .阻塞返回值:成功返回真正发送的数据长度,失败-1.
读函数
函数原型:
ssize_t recv(int s, void *buf, size_t len, int flags);
参数:
s: 通信的套接字
buf:存放接收数据的缓冲区
len: 数据长度
flags: 一般赋 0 .阻塞
返回值:成功返回真正接收的数据长度,失败-1.
2 TCP客户端编写流程
2.1 创建套接字
头文件:
#include <sys/socket.h>
函数原型:
int socket(int domain, int type, int protocol);
参数:
domain: 网域
AF_INET : IPv4
AF_INET6 : IPv6
type: 选择传输协议 tcp /udp
SOCK_STREAM ; tcp
SOCK_DGRAM : udp
protocol: 基本废弃, 一般赋 0返回值:成功返回套接字fd,失败返回-1
2.2 连接
函数原型:
int connect(int sockfd, const struct sockaddr*serv_addr, socklen_t addrlen);
参数:
sockfd 为前面 socket 的返回值, 即 fd
serv_addr 为结构体指针变量, 存储着远程服务器的 IP 与端口号信息。
addrlen 表示结构体变量的长度
返回值:成功则返回0,失败返回-1
举例:
int fd = socket(AF_INET,SOCK_STREAM,0);
#define PORT 33333
#define IP "192.168.110.123"
struct sockaddr_in ser_addr;
ser_addr.sin_family = AF_INET;
ser_addr.sin_port = htons(PORT);
ser_addr.sin_addr.s_addr = inet_addr(IP);
connect(fd,(struct sockaddr *)&ser_addr,sizeof(ser_addr));
TCP 问题: 当服务器结束之后, 再次运行会出现 bind 错误(地址被占用)
解决办法:
int sfd = socket(AF_INET,SOCK_STREAM,0);
创建套接字之后, 用:
int val = 1;
setsockopt(sfd,SOL_SOCKET,SO_REUSEADDR,&val,sizeof(val));
setsockopt(sfd,SOL_SOCKET,SO_REUSEPORT,&val,sizeof(val));
3.UDP
特点:非面向连接,不可靠传输,传输速率高
分别有:
单播 -- 一对一 广播 -- 一对多 组播 -- 多对多
3.1 单播
UDP不像TCP。无需在连接状态下交换数据,因此基于UDP的接收方和发送发也无需经过连接过程。也就是说,不必调用listen()和accept()函数。UDP中有创建套接字的过程和数据交换的过程。不管是接收方还是发送方都只需要1个套接字。
3.1.1 创建套接字
头文件:
#include <sys/socket.h>
函数原型:
int socket(int domain, int type, int protocol);
参数:
domain: 网域
AF_INET : IPv4
AF_INET6 : IPv6
type: 选择传输协议 tcp /udp
SOCK_STREAM ; tcp
SOCK_DGRAM : udp
protocol: 基本废弃, 一般赋 0
返回值: 成功返回套接字 fd, 失败返回
3.1.2 bind:绑定自己的IP和端口
发送方:socket 同接收方
函数原型:
ssize_t sendto(
int s, //套接字
const void *buf, //要发送的数据的首地址
size_t len, //数据的大小
int flags, // 0
const struct sockaddr *to,//接收方的 IP 和 port
socklen_t tolen //上一个参数的大小
);
返回值:成功返回真正发送的数据长度,失败-1
ssize_t recvfrom(
int s, //套接字
void *buf, //接受的内容存放的位置的首地址
size_t len, //接收的大小
int flags, //0
struct sockaddr *from, //提供空间即可, 存放发送方的 IP 和 PORT
socklen_t *fromlen//上一个参数的大小, 但是填指针
);
返回值:成功返回真正接收的数据长度, 失败-1
3.2 广播
udp具有广播功能,即一个发送方,多个接收方;
广播:处于局部网络中的所有设备都可以接收消息
广播的地址:网络号不变,主机号为255
socket 创建的 UDP 套接字支持组播和广播, 但是想要使用广播, 必须用 setsockopt 设置广播的功能。
接收方:
1》 创建套接字 socket UDP
2》 绑定 IP 和 PORT IP 填 INADDR_ANY
3》 recvfrom 接收发送方:
1》 创建套接字 socket UDP
2》 开启广播功能: setsockopt
开启广播功能函数
int setsockopt(int sockfd,int level,int optname,const void *optval,socklen_t optlen);
函数功能:设置套接字的选项
参数:
sockfd创建的套接字
level--选项所在的级别:
想让套接字有广播功能, 就必须把 level 设置为 SOL_SOCKET
SOL_SOCKET -- 广播功能所在的级别
optname--选项所在的名称:
SO_BROADCAST (广播功能)
optval: 整数的地址
int num = 1;//开启功能 &num
0--失能(关闭此功能,系统默认关闭)
1--使能(开启此功能)
optlen: optval 的大小 //sizeof(num);
举例:
int a = 1;
setsockopt(fd,SOL_SOCKET,SO_BROADCAST,&a,sizeof(int));
sendto 发送
3.3 多播
对一组特定的主机发送消息 比如: 直播, 多播(D 类), IP 地址分为: 224.0.0.0~239.255.255.255
1》 创建套接字 socket UDP
int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);
函数功能: 设置套接字的选项
参数:
sockfd: socket 创建的套接字
level: 级别
SOL_SOCKET : 广播级别
IPPROTO_IP : 组播级别
optname: 选项由 level 决定, level 选的是 IPPROTO_IP, optname 有两种选择
IP_ADD_MEMBERSHIP 加入组播
IP_MULTICAST_IF 创建组播
optval: 设置参数 由 optname 决定
当 optname 为:
IP_ADD_MEMBERSHIP( 加入组播) , optval 是 struct ip_mreqn 这个结构体
IP_MULTICAST_IF ( 创建组播) , optval 也是 struct ip_mreqn 这个结构体
struct ip_mreqn
{
struct in_addr imr_multiaddr;//多播组的地址 224.0.0.0-239.255.255.255 struct
in_addr imr_address;//本地的 IP 地址, 填固定的宏 INADDR_ANY 即可
int imr_ifindex; //网卡编号( 物理硬件地址) , 可以用物理硬件 ID 函数: if_nametoindex("ens33"); 其中 ens33 是网卡的
名字, 通过名字获取编号
};