Socket套接字常用API
1. 网络通信
网络通信的主体主要分为两个部分:客户端和服务器端。
通信双方通过IP和port(端口)来进行通信。IP地址用来确定某一台主机,端口用来确定通信主机的某一进程。
1.1 字节序
字节序是指多字节数据在计算机中存储或者网络传输时各字节的存储顺序。(对于单字节来说没有字节序问题,例如:单字符,字符串)
计算机硬件有两种存储数据的方式:大端字节序(big endian)和小端字节序(little endian)。
大端字节序:数据的低位字节存储到内存的高地址位,数据的高位字节存储到内存的低地址位。
小端字节序:数据的低位字节存储到内存的低地址位,数据的高位字节存储到内存的高地址位。
计算机存储数据默认使用小端字节序,而套接字通信时,操作的数据大多是大端字节序(例如:IP地址、端口号、通信数据)。
1.2 字节序转换函数
#include <arpa/inet.h>
// 主机字节序->网络字节序
uint16_t htons(uint16_t hostshort); // 短整形
uint32_t htonl(uint32_t hostlong); // 整形
// 网络字节序->主机字节序
uint16_t ntohs(uint16_t netshort); // 短整形
uint32_t ntohl(uint32_t netlong); // 整形
1.3 IP地址转换函数
#include <arpa/inet.h>
// 主机字节序的IP地址(字符串)转换为网络字节序的IP地址(整形)
/*
* af:地址族
* AF_INET:ipv4格式的IP地址
* AF_INET6:ipv6格式的IP地址
* src:(传入参数)主机字节序的IP地址(字符串),xxx.xxx.xxx.xxx
* dst:(传出参数)网络字节序的IP地址(整形)
* 返回值:成功返回1,失败返回0或者-1
*/
int inet_pton(int af, const char *src, void *dst);
// 网络字节序的IP地址(整形)转换为主机字节序的IP地址(字符串)
/*
* af:地址族
* AF_INET:ipv4格式的IP地址
* AF_INET6:ipv6格式的IP地址
* src:(传入参数)网络字节序的IP地址(整形)
* dst:(传出参数)主机字节序的IP地址(字符串),xxx.xxx.xxx.xxx
* size:函数第三个参数dst的内存大小
* 返回值:成功返回指向第三个参数dst对应的内存地址,失败返回NULL
*/
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
// 主机字节序的IP地址(字符串)转换为网络字节序的IP地址(整形),仅适用于IPv4
in_addr_t inet_addr(const char *cp);
// 网络字节序的IP地址(整形)转换为主机字节序的IP地址(字符串),仅适用于IPv4
char *inet_ntoa(struct in_addr in);
1.3 sockaddr数据结构
struct sockaddr
{
sa_family_t sa_family; // 地址族协议
char sa_data[14]; // 端口(2字节)+IP地址(4字节)+保留字节(8字节),ipv4
}
struct in_addr
{
in_addr_t s_addr;
}
struct sockaddr_in
{
sa_family_t sin_family; // 地址族协议
in_port_t sin_port; // 端口号
struct in_addr sin_addr; // IP地址
unsigned char sin_zero[8];// 保留字节,ipv4
}
2. socket套接字API
// 头文件
// accept(2), bind(2), connect(2), listen(2), socket(2)
// 以上函数均被此头文件包含
#include <sys/types.h>
#include <sys/socket.h>
2.1 创建套接字
// 创建一个用于通信的文件描述符--create an endpoint for communication
/*
* domain:通信使用的地址族协议
* AF_INET:使用IPv4格式的IP地址
* AF_INET6:使用IPv6格式的IP地址
* type:
* SOCK_STREAM: 使用流式的传输协议
* SOCK_DGRAM: 使用报式 (报文) 的传输协议
* protocol:一般写0即可,使用默认的协议
* SOCK_STREAM: 流式传输默认使用的是 tcp
* SOCK_DGRAM: 报式传输默认使用的 udp
* return:
* 成功:返回用于通信的套接字文件描述符
* 失败:返回-1,错误代码存入errno中
*/
int socket(int domain, int type, int protocol);
2.2 绑定
// 将由socket创建的文件描述符与本地IP和端口进行绑定
/*
* sockfd:需要绑定的套接字文件描述符(通过调用socket()生成的文件描述符)
* addr:需要绑定的IP和端口,常使用struct sockaddr_in结构体。
* ----------特别注意:IP和端口要转换为网络字节序----------
* addrlen:第二个参数addr指向的内存大小,sizeof(struct sockaddr)
* return:
* 成功:返回0
* 失败:返回-1,错误代码存入errno中
*/
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
2.3 监听
// 服务器端设置监听,来接受来自客户端的连接
/*
* sockfd:设置监听的套接字文件描述符(通过调用socket()生成的文件描述符),在监听之前必须先绑定bind()
* backlog:可以同时监听的最大连接数,最大值位128
* return:
* 成功:返回0
* 失败:返回-1
*/
int listen(int sockfd, int backlog);
2.4 accept
// 等待并接受客户端连接
/*
* sockfd:处于监听的文件描述符
* addr:传出参数,建立连接的客户端的地址信息
* addrlen:第二个参数addr指向的内存大小,sizeof(struct sockaddr)
* return:
* 成功:返回建立连接的用于通信的套接字文件描述符
* 失败:返回-1,错误码
*/
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
// accept()是一个阻塞函数,没有新客户端请求连接时,该函数一直阻塞;当检测到新客户端连接请求时,阻塞解除。
2.5 发送数据
2.5.1 linux下的文件操作
#include <unistd.h>
// write()会把参数buf所指向内存写入count个字节到参数fd所指向的文件内
/*
* fd:需要写入的文件描述符
* buf:需要写入的内存块
* count:需要写入的字节大小
* return:
* 成功:返回实际写入的字节数
* 失败:返回-1,错误代码存入errno中
*/
ssize_t write(int fd, const void *buf, size_t count);
2.5.2 socket通信函数
#include <sys/types.h>
#include <sys/socket.h>
/*
* fd:用于通信的套接字文件描述符
* buf:存放要发送的数据内存的地址
* len:要发送的数据内存大小,参数buf所指向的内存大小
* flags:特殊的属性,一般不使用,指定为0
* return:
* 成功:返回实际发送的字节数
* 失败:返回-1,错误代码存入errno中
*/
// send()函数用于有连接的通信
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
// sendto函数用于无连接的通信,也可用于有连接的通信
/*
* dest_addr:用于通信的目标地址信息
* addrlen:参数dest_addr的大小
*/
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
// send(sockfd, buf, len, flags); 等价于 sendto(sockfd, buf, len, flags, NULL, 0);
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
2.6 接受数据
2.6.1 linux下的文件操作
#include <unistd.h>
// read()会把参数fd所指的文件传送count个字节到buf指针所指的内存中
/*
* fd:需要读取的文件描述符
* buf:用来存放读取内容的把内存块
* count:读取的字节大小
* return:
* 成功:返回实际读取的字节数
* 如果返回0,则表示已到达文件尾或是无可读取的数据
*/
ssize_t read(int fd, void *buf, size_t count);
2.6.2 socket通信函数
#include <sys/types.h>
#include <sys/socket.h>
/*
* fd:用于通信的套接字文件描述符
* buf:要接收的数据内存地址
* len:要接收的数据内存大小,参数buf所指向的内存大小
* flags:特殊的属性,一般不使用,指定为0
* return:
* 大于0:返回实际接收的字节数
* 等于0:对方断开了连接
* 返回-1:接收失败,错误代码存入errno中
*/
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
// recvfrom函数用于无连接的通信,也可用于有连接的通信
/*
* dest_addr:用于通信的目标地址信息
* addrlen:参数dest_addr的大小
*/
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
2.7 connect
/*
* sockfd:用于通信的套接字文件描述符(通过调用socket()生成的文件描述符)
* addr:需要建立连接的服务器的地址信息
* addrlen:参数addr的内存大小
* return:
* 成功:返回0
* 失败:返回-1,错误码
*/
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
2.8 关闭文件描述符
#include <unistd.h>
// close()关闭文件描述符,使其不再引用任何文件,方便以后重新建立使用。
/*
* close a file descriptor
* fd:需要关闭的文件描述符
* return:
* 成功:返回0
* 失败:返回-1,错误代码存入errno中
*/
int close(int fd);
3. TCP/UDP通信协议
3.1 TCP通信
TCP是一个面向连接的、安全的、流式传输协议。建立连接时有三次握手,断开连接时有四次挥手。
TCP通信流程
3.2 UDP通信
UDP是一个无连接的、不安全的、报式传输协议。
UDP通信流程
4. 例程
4.1 TCP通信
4.1.1 TCP服务器
/******************************************************************************
Copyright (C), 2022, HuJH.
******************************************************************************
File Name : serverTcp.c
Version : Initial Draft
Author : HuJH
Created : 2022
Description : socket套接字通信--TCP服务器端
******************************************************************************/
#include <stdio.h>
#include <string.h>
//----------socket所需要的头文件----------//
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#define SERVER_IP "192.168.41.231"
#define SERVER_PORT 8080
#define BACKLOG 128
/******************************************************************************
* function : main()
* Description : TCP服务器端程序
******************************************************************************/
int main(int argc, char **argv)
{
int sfd, cfd;
struct sockaddr_in sAddr; // 服务器地址信息
struct sockaddr_in cAddr; // 客户端地址信息
ssize_t recvLen; // 接收字节的长度
unsigned char recvBuf[1000];// 接收字节缓存区
// 1.创建socket套接字
sfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sfd)
{
printf("Create socket failed!\n");
return -1;
}
// 2.将套接字与本地IP和端口绑定
memset(&sAddr, 0, sizeof(struct sockaddr_in));
sAddr.sin_family = AF_INET;
sAddr.sin_addr.s_addr = inet_addr(SERVER_IP);
sAddr.sin_port = htons(SERVER_PORT);
if(-1 == bind(sfd, (const struct sockaddr *)&sAddr, sizeof(struct sockaddr)))
{
printf("Bind local IP and Port failed!\n");
return -1;
}
// 3.设置监听
if (-1 ==listen(sfd, BACKLOG))
{
printf("Set server listen failed!\n");
return -1;
}
// 4.阻塞等待并接受客户端连接
socklen_t addrLen = sizeof(struct sockaddr);
cfd = accept(sfd, (struct sockaddr *)&cAddr, &addrLen);
if (-1 == cfd)
{
printf("Receive client connection failed!\n");
return -1;
}
printf("Get connect from client'IP: %s, Port: %d\n", inet_ntoa(cAddr.sin_addr), ntohs(cAddr.sin_port));
// 5.开始通信
while (1)
{
recvLen = recv(cfd, recvBuf, 999, 0);
if (recvLen <= 0)
{
printf("Receive data from client failed!\n");
break;
}
else
{
recvBuf[recvLen] = '\0';
printf("Client say:%s\n", recvBuf);
}
if (!strcmp(recvBuf, "q\0"))
break;
}
// 6.关闭文件描述符
close(cfd);
close(sfd);
return 0;
}
4.1.2 TCP客户端
/******************************************************************************
Copyright (C), 2022, HuJH.
******************************************************************************
File Name : clientTcp.c
Version : Initial Draft
Author : HuJH
Created : 2022
Description : socket套接字通信--TCP客户端
******************************************************************************/
#include <stdio.h>
#include <string.h>
//----------socket所需要的头文件----------//
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#define SERVER_IP "192.168.41.189"
#define SERVER_PORT 8234
/******************************************************************************
* function : main()
* Description : TCP客户端程序
******************************************************************************/
int main(int argc, char **argv)
{
int cfd; // 用于通信的套接字文件描述符
struct sockaddr_in sAddr; // 服务器地址信息
ssize_t sendLen; // 发送字节的长度
unsigned char sendBuf[1000];// 发送字节缓存区
// 1.创建socket套接字
cfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == cfd)
{
printf("Create socket failed!\n");
return -1;
}
// 2.与服务器建立连接
memset(&sAddr, 0, sizeof(struct sockaddr_in));
sAddr.sin_family = AF_INET;
sAddr.sin_addr.s_addr = inet_addr(SERVER_IP);
sAddr.sin_port = htons(SERVER_PORT);
if (-1 == connect(cfd, (const struct sockaddr *)&sAddr, sizeof(struct sockaddr)))
{
printf("Build connect failed!\n");
return -1;
}
// 3.开始通信
while (1)
{
printf("请输入发送给服务器的信息(输入q退出):\n");
if (NULL != fgets(sendBuf, 999, stdin))
{
// printf("%s", sendBuf);
sendLen = send(cfd, sendBuf, strlen(sendBuf), 0);
if (sendLen <= 0)
{
printf("Send info failed!\n");
continue;
}
if (!strcmp(sendBuf, "q\n"))
break;
}
}
// 4.关闭通信的文件描述符
close(cfd);
return 0;
}
4.2 UDP通信
4.2.1 UDP服务器
/******************************************************************************
Copyright (C), 2022, HuJH.
******************************************************************************
File Name : serverUdp.c
Version : Initial Draft
Author : HuJH
Created : 2022
Description : socket套接字通信--UDP服务器端
******************************************************************************/
#include <stdio.h>
#include <string.h>
//----------socket所需要的头文件----------//
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#define SERVER_IP "192.168.41.231"
#define SERVER_PORT 8080
/******************************************************************************
* function : main()
* Description : UDP服务器端程序
******************************************************************************/
int main(int argc, char **argv)
{
int sfd, cfd;
struct sockaddr_in sAddr; // 服务器地址信息
struct sockaddr_in cAddr; // 客户端地址信息
ssize_t recvLen; // 接收字节的长度
unsigned char recvBuf[1000];// 接收字节缓存区
// 1.创建socket套接字
sfd = socket(AF_INET, SOCK_DGRAM, 0);
if (-1 == sfd)
{
printf("Create socket failed!\n");
return -1;
}
// 2.将套接字与本地IP和端口绑定
memset(&sAddr, 0, sizeof(struct sockaddr_in));
sAddr.sin_family = AF_INET;
sAddr.sin_addr.s_addr = inet_addr(SERVER_IP);
sAddr.sin_port = htons(SERVER_PORT);
if(-1 == bind(sfd, (const struct sockaddr *)&sAddr, sizeof(struct sockaddr)))
{
printf("Bind local IP and Port failed!\n");
return -1;
}
// 3.开始通信
while (1)
{
socklen_t addrLen = sizeof(struct sockaddr);
recvLen = recvfrom(sfd, recvBuf, 999, 0, (struct sockaddr *)&cAddr, &addrLen);
if (recvLen <= 0)
{
printf("Receive data from client failed!\n");
break;
}
else
{
printf("Get connect from client'IP: %s, Port: %d\n", inet_ntoa(cAddr.sin_addr), ntohs(cAddr.sin_port));
recvBuf[recvLen] = '\0';
printf("Client say:%s\n", recvBuf);
}
if (!strcmp(recvBuf, "q\0"))
break;
}
// 4.关闭文件描述符
close(cfd);
close(sfd);
return 0;
}
4.2.2 UDP客户端
/******************************************************************************
Copyright (C), 2022, HuJH.
******************************************************************************
File Name : clientUdp.c
Version : Initial Draft
Author : HuJH
Created : 2022
Description : socket套接字通信--UDP客户端
******************************************************************************/
#include <stdio.h>
#include <string.h>
//----------socket所需要的头文件----------//
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#define SERVER_IP "192.168.41.189"
#define SERVER_PORT 8234
/******************************************************************************
* function : main()
* Description : UDP客户端程序
******************************************************************************/
int main(int argc, char **argv)
{
int cfd; // 用于通信的套接字文件描述符
struct sockaddr_in sAddr; // 服务器地址信息
ssize_t sendLen; // 发送字节的长度
unsigned char sendBuf[1000];// 发送字节缓存区
// 1.创建socket套接字
cfd = socket(AF_INET, SOCK_DGRAM, 0);
if (-1 == cfd)
{
printf("Create socket failed!\n");
return -1;
}
// 2.1与服务器建立连接(也可以不建立连接)
memset(&sAddr, 0, sizeof(struct sockaddr_in));
sAddr.sin_family = AF_INET;
sAddr.sin_addr.s_addr = inet_addr(SERVER_IP);
sAddr.sin_port = htons(SERVER_PORT);
// 2.2与服务器不建立连接
#if 0
if (-1 == connect(cfd, (const struct sockaddr *)&sAddr, sizeof(struct sockaddr)))
{
printf("Build connect failed!\n");
return -1;
}
#endif
// 3.开始通信
while (1)
{
printf("请输入发送给服务器的信息(输入q退出):\n");
if (NULL != fgets(sendBuf, 999, stdin))
{
// printf("%s", sendBuf);
// 3.1 与服务器建立连接的通信方式
// sendLen = send(cfd, sendBuf, strlen(sendBuf), 0);
// 3.2 与服务器不建立连接的通信方式
sendLen = sendto(cfd, sendBuf, strlen(sendBuf), 0, (const struct sockaddr *)&sAddr, sizeof(struct sockaddr));
if (sendLen <= 0)
{
printf("Send info failed!\n");
continue;
}
if (!strcmp(sendBuf, "q\n"))
break;
}
}
// 4.关闭通信的文件描述符
close(cfd);
return 0;
}