获得一个 Socket 文件描述符对象,文件描述符是 Linux 操作文件的句柄。在 Linux 中一切皆文件,Socket 文件描述符对象就是操作 Socket 的句柄。
int socket(int af, int type, int protocol);
创建 TCP 套接字:
int tcp_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
创建 UDP 套接字:
int udp_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
将 Socket 与主机中的某个 IP:Port 绑定起来。
int bind(int sock, struct sockaddr *addr, socklen_t addrlen);
将创建的套接字 ServerSock 与本地 IP、端口进行绑定:
int ServerSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
struct sockaddr_in ServerSockAddr;
memset(&ServerSockAddr, 0, sizeof(ServerSockAddr));
ServerSockAddr.sin_family = PF_INET;
ServerSockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
ServerSockAddr.sin_port = htons(1314);
bind(ServerSock, (SOCKADDR*)&ServerSockAddr, sizeof(SOCKADDR));
其中 struct sockaddr_in 类型的结构体变量用于保存 IPv4 的 IP 信息。
struct in_addr {
unsigned long a_addr;
}
struct sockaddr_in {
unsigned short sin_family; // 地址类型(2B)
unsigned short int sin_port; // 端口号(2B)
struct in_addr sin_addr; // IP 地址(4B)
unsigned char sin_zero[8]; // 填充空间(8B)
}
struct sockaddr {
unsigned short sa_family; // 地址类型(2B)
char sa_data[14]; // 协议地址(14B)
}
先初始化 sockaddr_in,再将它强制转化成 sockaddr 来使用,例如 (SOCKADDR*)&ServerSockAddr
,这里涉及到了结构体之间的数据类型转换。这两个结构体,长度都为 16 字节,sockaddr_in.sin_family 的数据存入 sockaddr.sa_family,剩下的 14 个字节存入 sockaddr.sa_data,这样在各种操作中可以方便的处理端口号和 IP 地址。
若是 IPv6,则有对应的结构体:
struct sockaddr_in6
{
sa_family_t sin6_family; // 地址类型,取值为 AF_INET6
in_port_t sin6_port; // 16 位端口号
uint32_t sin6_flowinfo; // IPv6 流信息
struct in6_addr sin6_addr; // 具体的 IPv6 地址
uint32_t sin6_scope_id; // 接口范围 ID
};
客户端连接到服务端的某个 Socket,所以下述的 struct sockaddr *serv_addr
填充服务端的信息。
int connect(int sock, struct sockaddr *serv_addr, socklen_t addrlen);
示例:
int ClientSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
connect(ClientSock, (SOCKADDR*)&ServerSockAddr, sizeof(SOCKADDR));
服务端进程监听 Socket 是否有新的,由客户端发起的连接请求。
int listen(int sock, int backlog);
服务端接受客户端的连接请求,并返回一个客户端的 Socket 文件描述符,该描述符作为服务端操作对应该客户端的连接的句柄。
int accept(int sock, struct sockaddr *addr, socklen_t *addrlen);
示例:
/* 监听客户端请求,accept() 返回一个新的套接字,发送和接收都是用这个套接字 */
int ClientSock = accept(ServerSock, (SOCKADDR*)&ClientAddr, &len);
服务端完成通信任务之后关闭与某个客户端的连接,释放资源。所以 int fd
传入的是某个客户端的 Socket 文件描述符。
int close(int fd);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t sendto(int sock, void *buf, size_t nbytes, int flags, struct sockaddr *to, socklen_t addrlen);
ssize_t recvfrom(int sock, void *buf, size_t nbytes, int flags, struct sockadr *from, socklen_t *addrlen);
#include
#include
#include
#include
#include
#include
#define BUF_LEN 100 /* Size of buffer. */
/* Print exception information. */
#define ERR_MSG(errnum) do { \
errnum = errno; \
fprintf(stderr, "ERROR num: %d\n", errnum); \
perror("PERROR message"); \
fprintf(stderr, "STRERROR message: %s\n", strerror(errnum)); \
} while (0)
extern int errno;
int main(void)
{
int server_fd = 0;
int client_fd = 0;
char buf[BUF_LEN] = {0};
int addr_len = 0;
int recv_len = 0;
int optval = 1;
struct sockaddr client_addr;
memset(&client_addr, 0, sizeof(struct sockaddr));
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(struct sockaddr));
/* 创建 TCP 服务端 Socket 文件描述符。 */
if (-1 == (server_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP))) {
printf("socket ERROR.\n");
ERR_MSG(errno);
exit(1);
}
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // INADDR_ANY,即 0.0.0.0 表示监听本机所有的 IP 地址,在生产环境中不建议使用。
server_addr.sin_port = htons(6666);
/* 设置地址和端口号可以重复使用,回避了端口可能冲突的问题,在生产环境中不建议使用。*/
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR,
&optval, sizeof(optval)) < 0) {
printf("setsockopt ERROR.\n");
ERR_MSG(errno);
exit(1);
}
if (-1 == (bind(server_fd, (struct sockaddr*)&server_addr,
sizeof(struct sockaddr)))) {
printf("bind ERROR.\n");
ERR_MSG(errno);
exit(1);
}
if (-1 == (listen(server_fd, 10))) {
printf("listen ERROR.\n");
ERR_MSG(errno);
exit(1);
}
addr_len = sizeof(struct sockaddr);
while (1) {
/* 监听某个客户端的连接请求,返回客户端 Socket 文件描述符,对该客户端的发送和接收都使用这个套接字。 */
if (-1 == (client_fd = (accept(server_fd,
(struct sockaddr*)&client_addr,
&addr_len)))) {
printf("accept ERROR.\n");
ERR_MSG(errno);
exit(1);
}
if ((recv_len = recv(client_fd, buf, BUF_LEN, 0)) < 0) {
printf("recv ERROR.\n");
ERR_MSG(errno);
exit(1);
}
printf("Client sent data %s\n", buf);
send(client_fd, buf, recv_len, 0);
/* 关闭套接字。 */
close(client_fd);
memset(buf, 0, BUF_LEN);
}
return 0;
}
#include
#include
#include
#include
#include
#include
#define BUF_LEN 100 /* Size of buffer. */
/* Print exception information. */
#define ERR_MSG(errnum) do { \
errnum = errno; \
fprintf(stderr, "ERROR num: %d\n", errnum); \
perror("PERROR message"); \
fprintf(stderr, "STRERROR message: %s\n", strerror(errnum)); \
} while (0)
extern int errno;
int main(void)
{
int client_fd;
char buf[BUF_LEN] = {0};
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(struct sockaddr));
/* 连接到指定的服务端。 */
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
server_addr.sin_port = htons(6666);
while (1) {
if (-1 == (client_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP))) {
printf("socket ERROR.\n");
ERR_MSG(errno);
exit(1);
}
/* 向指定的服务端发出连接请求。 */
if (-1 == (connect(client_fd, (struct sockaddr*)&server_addr,
sizeof(struct sockaddr)))) {
printf("connect ERROR.\n");
ERR_MSG(errno);
exit(1);
}
printf("Send to client >");
gets(buf);
send(client_fd, buf, BUF_LEN, 0);
memset(buf, 0, BUF_LEN);
recv(client_fd, buf, BUF_LEN, 0);
printf("Receive from server: %s\n", buf);
memset(buf, 0, BUF_LEN);
close(client_fd);
}
return 0;
}
编译:
gcc tcp_server.c -o tcp_server
gcc tcp_client.c -o tcp_client
运行:
# ./tcp_server
$ netstat -lpntu | grep 6666
tcp 0 0 0.0.0.0:6666 0.0.0.0:* LISTEN 28675/./tcp_server
# ./tcp_client
#include
#include
#include
#include
#include
#include
#include
#define BUF_LEN 100
int main(void)
{
int ServerFd;
char Buf[BUF_LEN] = {0};
struct sockaddr ClientAddr;
struct sockaddr_in ServerSockAddr;
int addr_size = 0;
int optval = 1;
/* 创建 UDP 服务端 Socket */
if ( -1 == (ServerFd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)))
{
printf("socket error!\n");
exit(1);
}
/* 设置服务端信息 */
memset(&ServerSockAddr, 0, sizeof(ServerSockAddr)); // 给结构体ServerSockAddr清零
ServerSockAddr.sin_family = AF_INET; // 使用IPv4地址
ServerSockAddr.sin_addr.s_addr = htonl(INADDR_ANY); // 自动获取IP地址
ServerSockAddr.sin_port = htons(1314); // 端口
// 设置地址和端口号可以重复使用
if (setsockopt(ServerFd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0)
{
printf("setsockopt error!\n");
exit(1);
}
/* 绑定操作,绑定前加上上面的socket属性可重复使用地址 */
if (-1 == bind(ServerFd, (struct sockaddr*)&ServerSockAddr, sizeof(ServerSockAddr)))
{
printf("bind error!\n");
exit(1);
}
addr_size = sizeof(ClientAddr);
while (1)
{
/* 接受客户端的返回数据 */
int str_len = recvfrom(ServerFd, Buf, BUF_LEN, 0, &ClientAddr, &addr_size);
printf("客户端发送过来的数据为:%s\n", Buf);
/* 发送数据到客户端 */
sendto(ServerFd, Buf, str_len, 0, &ClientAddr, addr_size);
/* 清空缓冲区 */
memset(Buf, 0, BUF_LEN);
}
close(ServerFd);
return 0;
}
#include
#include
#include
#include
#include
#include
#define BUF_LEN 100
int main(void)
{
int ClientFd;
char Buf[BUF_LEN] = {0};
struct sockaddr ServerAddr;
int addr_size = 0;
struct sockaddr_in ServerSockAddr;
/* 创建客户端socket */
if (-1 == (ClientFd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)))
{
printf("socket error!\n");
exit(1);
}
/* 向服务器发起请求 */
memset(&ServerSockAddr, 0, sizeof(ServerSockAddr));
ServerSockAddr.sin_family = PF_INET;
ServerSockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
ServerSockAddr.sin_port = htons(1314);
addr_size = sizeof(ServerAddr);
while (1)
{
printf("请输入一个字符串,发送给服务端:");
gets(Buf);
/* 发送数据到服务端 */
sendto(ClientFd, Buf, strlen(Buf), 0, (struct sockaddr*)&ServerSockAddr, sizeof(ServerSockAddr));
/* 接受服务端的返回数据 */
recvfrom(ClientFd, Buf, BUF_LEN, 0, &ServerAddr, &addr_size);
printf("服务端发送过来的数据为:%s\n", Buf);
memset(Buf, 0, BUF_LEN); // 重置缓冲区
}
close(ClientFd); // 关闭套接字
return 0;
}
运行:
$ netstat -lpntu | grep 1314
udp 0 0 0.0.0.0:1314 0.0.0.0:* 29729/./udp_server