套接字(socket)是一种通信机制,是通信的两方的一种约定,socket屏蔽了各个协议的通信细节, 对用户进程提供了一套可以统一、方便的使用TCP/IP协议的接口。这使得程序员无需关注协议本身,直 接使用socket提供的接口与不同主机间的进程互联通信。
套接字描述符类似于文件描述符,UNIX、Linux把网络当文件看待,发送数据即写文件write,接收数 据即读文件read,销毁socket对象即关闭文件close,在UNIX、Linux系统下一切皆文件。
#include
int socket (int domain, int type, int protocol);
功能:在内核中创建一个套接字对象
domain:通信地址类型
AF_UNIX/AF_LOCAL/AF_FILE: 本地通信(进程间通信)
AF_INET: 基于IPv4(32位IP地址)的网络通信
AF_INET6: 基于IPv6(128位IP地址)的网络通信
type:通信协议类型
SOCK_STREAM: 数据流协议,即TCP协议
SOCK_DGRAM: 数据报协议,即UDP协议
protocol:特别通信协议,一般不用,置0即可
返回值:成功返回套接字描述符,失败返回-1
当socket对象创建好以后,它并不能立即被其它socket对象连接并通信,需要把一个地址与Socket对 象进行绑定,这样它才能被其它socket对象连接并通信。
// 基本地址类型,它是socket系列接口的表面参数,而实际使用的是sockaddr_un或sockaddr_in
struct sockaddr
{
sa_family_t sa_family; // 地址类型,与创建socket对象时的domain参数一至即可
char sa_data[14]; // 它是只是占位而已
};
// 本地地址类型
#include
struct sockaddr_un
{
sa_family_t sun_family;
char sun_path[]; // 套接字文件路径
};
// 网络地址类型
#include
struct sockaddr_in
{
sa_family_t sin_family;
in_port_t sin_port; // 端口号,用于区分通信方的进程,1024~65535
struct in_addr sin_addr; // IP地址
};
struct in_addr
{
in_addr_t s_addr; // 32位IPv4地址
};
typedef uint32_t in_addr_t;
#include
int bind (int sockfd, const struct sockaddr* addr,socklen_t addrlen);
功能:将套接字和通信地址绑定在一起,由被动连接的Socket对象调用
addr:sockaddr_un、sockaddr_in结构变量的地址
addrlen:地址结构变量的字节数,便于bind区分用户提供的是sockaddr_un或sockaddr_in
返回值:成功返回0,失败返回-1
#include
int connect (int sockfd, const struct sockaddr* addr,socklen_t addrlen);
功能:socket对象A连接socket对象B,由主动连接的Socket对象调用
sockfd:socket对象A的描述符,也就是连接的发起者
addr:socket对象B的通信地址,socket对象B必须与该通信地址绑定过
addrlen:地址结构变量的字节数
返回值:成功返回0,失败返回-1
主机字节序因处理器架构而不同,有的采用小端字节序,有的采用大端字节序,网络字节序则固定采 用大端字节序,所以在网络通信时需要把数据类型大于1字节的数据转换成网络字节序。
#include
// 32位无符号整数,主机字节序 -> 网络字节序
uint32_t htonl (uint32_t hostlong);
// 16位无符号整数,主机字节序 -> 网络字节序
uint16_t htons (uint16_t hostshort);
// 32位无符号整数,网络字节序 -> 主机字节序
uint32_t ntohl (uint32_t netlong);
// 16位无符号整数,网络字节序 -> 主机字节序
uint16_t ntohs (uint16_t netshort);
日常生活中使用IPv4地址是点分十进制格式,而在程序中使用的是32位4字节无符号整数,并且还要 是采用大端字节序。
#include
// 点分十进制字符串 -> 网络字节序32位无符号整数
in_addr_t inet_addr (const char* cp);
// 点分十进制字符串 -> 网络字节序32位无符号整数
int inet_aton (const char* cp, struct in_addr* inp);
// 网络字节序32位无符号整数 -> 点分十进制字符串
char* inet_ntoa (struct in_addr in);
由于TCP协议是基于面向连接的,而且在连接还需要进行三次握手,所以在通信前被连接Scoket对象 还需要开启监听和等待连接的步骤。
在TCP服务器编程中listen函数使主动连接套接字变为被动连接套接口,使得一个进程可以接受其它进 程的连接请求,从而成为一个服务器进程。
#include
int listen (int sockfd, int backlog);
sockfd: 一个已绑定未被连接的套接字描述符
backlog:连接请求队列(queue of pending connections)的最大长度(一般由2到4)
用SOMAXCONN则为系统给出的最大值,TCP为侦听套接字维护的两个队列
int accept (int sockfd, struct sockaddr* addr,socklen_t* addrlen);
功能:从sockfd参数所标识套接字的未决连接请求队列中,提取第一个连接请求。
同时创建一个新的套接字,用于在该连接中通信,返回该套接字的描述符。
addr:用于输出连接请求发起者的地址信息
addrlen:既是输入也是输出
输入:它负责告诉accept函数addr的容量
输出:它能从accept获取连接者的addr的字节数
返回值:成功返回通信套接字描述符,失败返回-1。
Socket对象的本质就是内核文件对象,所以发送数据可以write函数,接收数据可以使用read函数,关 闭Socket对象close,但Socket在write、read的基础上封装了自己发送数据、接收数据的函数。
ssize_t recv (int sockfd, void* buf, size_t len,int flags);
功能:通过sockfd参数所标识的套接字,期望接收len个字节到buf所指向的缓冲区中。
返回值:成功返回实际接收到的字节数,失败返回-1。
flags:暂时写0即可,后续详细介绍
ssize_t send (int sockfd, const void* buf,size_t len, int flags);
功能:通过sockfd参数所标识的套接字,从buf所指向的缓冲区中发送len个字节。
返回值:成功返回实际被发送的字节数,失败返回-1。
服务端代码:
#include
#include
#include
#include
#include
#include
#include
#include
#include
void error_exit(const char *func)
{
printf("%s:%m\n", func);
exit(EXIT_FAILURE);
}
void *run(void *arg)
{
int cli_fd = *(int *)arg;
char buf[4096];
for (;;)
{
// 接收数据
size_t ret = recv(cli_fd, buf, sizeof(buf), 0);
if (0 >= ret || 0 == strcmp("quit", buf))
break;
printf("recv:%s bytes:%u\n", buf, ret);
strcat(buf, ":return");
// 返回数据
ret = send(cli_fd, buf, strlen(buf) + 1, 0);
if (0 >= ret)
break;
}
printf("通信结束!\n");
// 关闭通信socket
close(cli_fd);
}
int main(int argc, const char *argv[])
{
if (2 != argc)
{
printf("Use:./a.out \n" );
return EXIT_FAILURE;
}
// 创建socket对象
int svr_fd = socket(AF_INET, SOCK_STREAM, 0);
if (0 > svr_fd)
error_exit("socket");
// 准备通信地址
struct sockaddr_in addr = {};
addr.sin_family = AF_INET;
addr.sin_port = htons(atoi(argv[1]));
addr.sin_addr.s_addr = inet_addr("10.0.2.15");
socklen_t addrlen = sizeof(addr);
// 绑定socket对象和通信地址
if (bind(svr_fd, (struct sockaddr *)&addr, addrlen))
error_exit("bind");
// 开启监听
if (listen(svr_fd, 3))
error_exit("listen");
for (;;)
{
// 等待连接
int cli_fd = accept(svr_fd, NULL, NULL);
if (0 > cli_fd)
error_exit("accept");
pthread_t tid;
pthread_create(&tid, NULL, run, &cli_fd);
usleep(1000);
}
// 关闭连接socket
close(svr_fd);
return EXIT_SUCCESS;
}
客户端代码:
#include
#include
#include
#include
#include
#include
#include
#include
void error_exit(const char* func)
{
printf("%s:%m\n",func);
exit(EXIT_FAILURE);
}
int main(int argc,const char* argv[])
{
// 创建socket对象
int cli_fd = socket(AF_INET,SOCK_STREAM,0);
if(0 > cli_fd)
error_exit("socket");
// 准备server的通信地址
struct sockaddr_in addr = {};
addr.sin_family = AF_INET;
addr.sin_port = htons(2123);
addr.sin_addr.s_addr = inet_addr("10.0.2.15");
socklen_t addrlen = sizeof(addr);
// 连接server
if(connect(cli_fd,(struct sockaddr*)&addr,addrlen))
error_exit("connect");
char buf[4096];
for(;;)
{
printf(">>>");
scanf("%s",buf);
// 发送数据
size_t ret = send(cli_fd,buf,strlen(buf)+1,0);
if(0 >= ret || 0 == strcmp("quit",buf))
break;
// 接收数据
ret = recv(cli_fd,buf,sizeof(buf),0);
if(0 >= ret)
break;
printf("recv:%s bytes:%u\n",buf,ret);
}
printf("通信结束!\n");
// 关闭socket对象
close(cli_fd);
return EXIT_SUCCESS;
}
UDP是无连接的,即通信时不需要创建连接,发送数据结束时也没有连接可以释放,所以减小了开 销和发送数据前的延时;
UDP采用最大努力交付,不保证可靠交付,因此主机不需要维护复杂的连接状态;
UDP是面向报文的,只在应用层交下来的报文前增加了首部后就向下交付IP层,不需要解决粘包的 问题。
UDP是无阻塞控制的,即使网络中存在阻塞,也不会影响发送端的发送频率
UDP支持一对一、一对多、多对一、多对多的交互通信;
UDP的首部开销小,只有8个字节,它比TCP的20个字节的首部要短;
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
功能:UDP专用的数据发送函数。
dest_addr:收件人的地址。
addrlen:地址长度。
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
功能:UDP专用的数据接收函数。
src_addr:发件人的地址,也是数据返回时的地址
addrlen:地址长度。
服务端:
#include
#include
#include
#include
#include
#include
int main(int argc, const char *argv[])
{
// 创建socket对象
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (0 > sockfd)
{
perror("socket");
return -1;
}
// 准备通信地址(本机的)
struct sockaddr_in addr = {}, src_addr = {};
addr.sin_family = AF_INET;
addr.sin_port = htons(5566);
addr.sin_addr.s_addr = inet_addr("192.168.56.103");
socklen_t addrlen = sizeof(addr);
// 绑定地址与socket对象
if (bind(sockfd, (struct sockaddr *)&addr, addrlen))
{
perror("bind");
return -1;
}
char buf[4096];
size_t buf_size = sizeof(buf);
for (;;)
{
// 接收数据
int size = recvfrom(sockfd, buf, buf_size, 0, (struct sockaddr
*)&src_addr, &addrlen);
buf[size] = '\0';
printf("from:%s recv:%s byte:%d\n", inet_ntoa(src_addr.sin_addr),
buf, size);
strcat(buf, ":return");
// 返回数据
sendto(sockfd, buf, strlen(buf) + 1, 0, (struct sockaddr
*)&src_addr, addrlen);
}
return 0;
}
客户端:
#include
#include
#include
#include
#include
#include
#include
int main(int argc, const char *argv[])
{
// 创建socket对象
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (0 > sockfd)
{
perror("socket");
return -1;
}
// 准备通信地址(目标的)
struct sockaddr_in addr = {};
addr.sin_family = AF_INET;
addr.sin_port = htons(5566);
addr.sin_addr.s_addr = inet_addr("192.168.56.103");
socklen_t addrlen = sizeof(addr);
char buf[4096];
size_t buf_size = sizeof(buf);
for (;;)
{
printf(">>>");
scanf("%s", buf);
if (0 == strcmp("quit", buf))
{
printf("通信结束!");
close(sockfd);
break;
}
sendto(sockfd, buf, strlen(buf)+1, 0, (struct sockaddr *)&addr,
addrlen);
int size = recvfrom(sockfd, buf, buf_size, 0, (struct sockaddr
*)&addr, &addrlen);
printf("from:%s recv:%s byte:%d\n", inet_ntoa(addr.sin_addr), buf,
size);
}
}
UPD协议底层是否需要连接操作,客户端但可以在Socket层面进行连接,连接后的Socket对象在后续 的通信过程中就不再需要通信地址了。
#include
#include
#include
#include
#include
#include
#include
int main(int argc,const char* argv[])
{
// 创建socket对象
int sockfd = socket(AF_INET,SOCK_DGRAM,0);
if(0 > sockfd)
{
perror("socket");
return -1;
}
// 准备通信地址(目标的)
struct sockaddr_in addr = {};
addr.sin_family = AF_INET;
addr.sin_port = htons(7777);
addr.sin_addr.s_addr = inet_addr("47.97.229.46");
socklen_t addrlen = sizeof(addr);
// 连接服务器
if(connect(sockfd,(struct sockaddr*)&addr,addrlen))
{
perror("connect");
return -1;
}
char buf[4096];
size_t buf_size = sizeof(buf);
for(;;)
{
printf(">>>");
scanf("%s",buf);
send(sockfd,buf,strlen(buf)+1,0);
if(0 == strcmp("quit",buf))
{
printf("通信结束!");
close(sockfd);
break;
}
int size = recv(sockfd,buf,buf_size,0);
printf("from:%s recv:%s
byte:%d\n",inet_ntoa(addr.sin_addr),buf,size);
}
}
网络Socket通信是把网卡抽象成Socket文件配合TCP/IP协议簇,能够使当前进程与其它计算机的进程 进行网络通信。
本地Socket通信是在文件系统中创建Socket文件,能够使当前进程与本机的其它进程进行通信(IPC 进程间通信)。
使用sockaddr_un类型的通信地址,当调用socket对象与通信地址绑定时,会自动创建socket文件。
// 本地地址类型
#include
struct sockaddr_un
{
sa_family_t sun_family;
char sun_path[]; // 套接字文件路径
};
#include
int bind (int sockfd, const struct sockaddr* addr,socklen_t addrlen);
#include
#include
#include
#include
int main(int argc,const char* argv[])
{
int sockfd = socket(AF_LOCAL,SOCK_STREAM,0);
if(0 > sockfd)
{
perror("socket");
return -1;
}
struct sockaddr_un addr = {};
addr.sun_family = AF_LOCAL;
strcpy(addr.sun_path,"tcp_socket");
socklen_t addrlen = sizeof(addr);
if(bind(sockfd,(struct sockaddr*)&addr,addrlen))
{
perror("bind");
return -1;
}
if(listen(sockfd,3))
{
perror("listen");
return -1;
}
for(;;)
{
int clifd = accept(sockfd,NULL,0);
if(0 > clifd)
{
perror("accept");
sleep(1);
continue;
}
if(fork())
continue;
char buf[4096];
size_t buf_size = sizeof(buf);
for(;;)
{
printf("recv...\n");
int ret = recv(clifd,buf,buf_size,0);
if(0 >= ret || 0 == strcmp("quit",buf))
{
printf("结束通信!");
break;
}
buf[ret] = '\0';
printf("recv:%s byte:%d\n",buf,ret);
strcat(buf,":return");
ret = send(clifd,buf,strlen(buf)+1,0);
if(0 >= ret)
{
printf("结束通信!");
break;
}
}
close(clifd);
return 0;
}
return 0;
}
#include
#include
#include
#include
int main(int argc,const char* argv[])
{
int sockfd = socket(AF_LOCAL,SOCK_STREAM,0);
if(0 > sockfd)
{
perror("sockfd");
return -1;
}
struct sockaddr_un addr = {};
addr.sun_family = AF_LOCAL;
strcpy(addr.sun_path,"tcp_socket");
socklen_t addrlen = sizeof(addr);
if(connect(sockfd,(struct sockaddr*)&addr,addrlen))
{
perror("connect");
return -1;
}
char buf[4096];
size_t buf_size = sizeof(buf);
for(;;)
{
printf(">>>");
scanf("%s",buf);
int ret = send(sockfd,buf,strlen(buf)+1,0);
if(0 >= ret || 0 == strcmp("quit",buf))
{
printf("通信结束!\n");
break;
}
ret = recv(sockfd,buf,buf_size,0);
if(0 >= ret)
{
printf("通信结束!\n");
break;
}
printf("recv:%s byte:%d\n",buf,ret);
}
close(sockfd);
return 0;
}
#include
#include
#include
#include
#include
int main(int argc,const char* argv[])
{
// 创建socket对象
int sockfd = socket(AF_LOCAL,SOCK_DGRAM,0);
if(0 > sockfd)
{
perror("socket");
return -1;
}
// 准备通信地址(本机的)
struct sockaddr_un addr = {}, src_addr = {};
addr.sun_family = AF_LOCAL;
strcpy(addr.sun_path,"udp_ctos");
socklen_t addrlen = sizeof(addr);
// 绑定地址与socket对象
if(bind(sockfd,(struct sockaddr*)&addr,addrlen))
{
perror("bind");
return -1;
}
char buf[4096];
size_t buf_size = sizeof(buf);
for(;;)
{
// 接收数据
int size = recvfrom(sockfd,buf,buf_size,0,(struct
sockaddr*)&src_addr,&addrlen);
printf("from:%s recv:%s byte:%d\n",src_addr.sun_path,buf,size);
strcat(buf,":return");
// 返回数据
sendto(sockfd,buf,strlen(buf)+1,0,(struct
sockaddr*)&src_addr,addrlen);
}
return 0;
}
#include
#include
#include
#include
#include
int main(int argc,const char* argv[])
{
// 创建socket对象
int sockfd = socket(AF_LOCAL,SOCK_DGRAM,0);
if(0 > sockfd)
{
perror("socket");
return -1;
}
// 准备通信地址(自己的)
struct sockaddr_un addr = {}, dest_addr = {};
addr.sun_family = AF_LOCAL;
strcpy(addr.sun_path,"udp_stoc");
socklen_t addrlen = sizeof(addr);
// 绑定地址与socket对象
if(bind(sockfd,(struct sockaddr*)&addr,addrlen))
{
perror("bind");
return -1;
}
// 目标的
dest_addr.sun_family = AF_LOCAL;
strcpy(dest_addr.sun_path,"udp_ctos");
char buf[4096];
size_t buf_size = sizeof(buf);
for(;;)
{
printf(">>>");
scanf("%s",buf);
if(0 == strcmp("quit",buf))
{
printf("通信结束!");
close(sockfd);
break;
}
sendto(sockfd,buf,strlen(buf)+1,0,(struct
sockaddr*)&dest_addr,addrlen);
int size = recvfrom(sockfd,buf,buf_size,0,(struct
sockaddr*)&dest_addr,&addrlen);
printf("recv:%s byte:%d\n",buf,size);
}
}