套接字是通信端点的抽象。文件描述符用open函数创建,而套接字描述符用socket函数创建。socket函数原型如下:
int socket(int domain, int type, int protocol);
// 返回值:成功返回套接字描述符,失败返回-1
domain域确定通信特性,不同的域表示地址的格式不同,表示域的常数以AF开头,表示地址族(address family):
type确定套接字类型,进一步确定通信特征:
protocol通常为0,表示按上述两个参数确定默认的协议,例如:
- 通信域为AF_INET,套接字类型为SOCK_STREAM,则默认协议为TCP。
- 通信域为AF_INET,套接字类型为SOCK_DGRAM,则默认协议为UDP。
关闭套接字的一端可用shutdown函数:
int shutdown(int sockfd, int how);
// 返回值:成功返回0,失败返回-1
how为SHUT_RD,关闭读端,无法从套接字读取数据;how为SHUT_WR,关闭写端,无法通过套接字发送数据。
关闭套接字描述符的方法和关闭文件描述符的方法相同,都是使用close函数。
不同的CPU,使用不同的字节序列来表示数据,分大端序和小端序。例如大端序的机器发送到小端序的机器上,则接收到的数据顺序会颠倒。为了避免这种情况,使网络传输与具体的机器类型无关,采用网络字节序。发送方把本机序转换为网络字节序后发送,接收方把接收到的网络字节序转换为本机序。TCP/IP采用大端字节序,即网络字节序,而我们常用的X86结构是小端模式。下图来自维基百科,描述得很形象:
TCP/IP应用程序提供了四个通用函数用于本地字节序和网络字节序之间的转换:
- htonl:本地32位整型转网络字节序
- htons:本地16位整型转网络字节序
- ntohl:网络字节序转32位整型本地字节序
- ntohs:网络字节序转16位整型本地字节序
以下是TCP和UDP程序的相关函数接口的调用过程。
1、TCP服务端:
- socket:建立一个传输端点,返回一个套接字描述符。
- bind:将套接字绑定到某个IP和端口,此IP和端口就作为监听对象。注意,由于系统会分配给客户端默认地址,所以客户端使用该函数意义不大。
- listen:将套接字转换成监听套接字,使得客户端的连接请求可被内核接受,上述三步为设置监听描述符的正常步骤。
- accept:休眠,等待客户连接被内核接受,三次握手完毕后返回一个新的套接字描述符,该描述符连接到调用connect的客户端,被称为已连接描述符。
- send:发数据。
- recv:收数据。
- close:关闭连接,引发四次握手。
2、TCP客户端:
- socket:创建套接字。
- connect:建立连接,指明目的,和服务端的accept函数对应。
- send:发数据。
- recv:收数据。
3、UDP服务端:
- socket:建立套接字。
- bind:绑定监听的IP和端口。
- recvfrom:阻塞等待数据的到来。
4、UDP客户端:
和TCP客户端基本一致。如果在发送之前使用了connect函数,那么发送报文的目的地址都是connect调用中所指定的地址,所以直接send函数进行发送。没用之前没有调用connect函数,发送用sendto函数,这个函数需要在参数中指定目的地址。
以下是一些例程。
TCP客户端:
/* 基于TCP协议的socket编程客户端代码 */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
/* 客户端:
* socket-->connect-->send/recv
*/
#define SERVER_PORT 8888
int main(int argc, char **argv)
{
int iRet;
int iSocketClient;
struct sockaddr_in tSocketServerAddr;
int iSendLen;
unsigned char ucSendBuf[1000];
if (argc != 2)
{
printf("Usage:\n");
printf("%s <server_ip>\n", argv[0]);
return -1;
}
/* 1. 建立socket */
iSocketClient = socket(AF_INET, SOCK_STREAM, 0);
/* 2. 与服务器建立TCP连接 */
tSocketServerAddr.sin_family = AF_INET; /* recommend */
tSocketServerAddr.sin_port = htons(SERVER_PORT); /* host to net short */
if (inet_aton(argv[1], &tSocketServerAddr.sin_addr) == 0) /* 字符串转服务器IP */
{
printf("Invalid server IP\n");
return -1;
}
bzero(tSocketServerAddr.sin_zero, 8);
/* 如果iSocketClient没有绑定地址,则connect会给调用者绑定一个默认地址 */
iRet = connect(iSocketClient, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));
if (iRet == -1)
{
/* 连接失败 */
printf("connect error\n");
return -1;
}
while (1)
{
if (fgets(ucSendBuf, 999, stdin))
{
/* send函数发送数据 */
iSendLen = send(iSocketClient, ucSendBuf, strlen(ucSendBuf), 0);
if (iSendLen == -1)
{
/* 函数出错 */
close(iSocketClient);
return -1;
}
}
}
return 0;
}
TCP服务端:
/* 基于TCP协议的socket编程服务器端代码 */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
/* 服务器:
* socket-->bind-->listen-->accept-->send/recv
*/
#define SERVER_PORT 8888
#define BACKLOG 10
int main(int argc, char **argv)
{
int iRet;
int iAddrLen;
int iSocketSever;
int iNewSocketSever;
struct sockaddr_in tSocketServerAddr;
struct sockaddr_in tSocketClientAddr;
int iRecvLen;
unsigned char ucRecvBuf[1000];
/* 子进程死后,会发送SIGCHLD信号给父进程,
* 这里设置为忽略SIGCHLD信号
*/
signal(SIGCHLD,SIG_IGN);
/* 1. 建立socket
* AF_INET: IPv4 Internet protocols
* SOCK_STREAM: TCP协议
* 0: 通常赋值
*/
iSocketSever = socket(AF_INET, SOCK_STREAM, 0);
if (iSocketSever == -1)
{
printf("socket error\n");
return -1;
}
/* 2. 配置socket
* 设置监听本机的哪个IP,端口
*/
tSocketServerAddr.sin_family = AF_INET; /* recommend */
tSocketServerAddr.sin_port = htons(SERVER_PORT); /* host to net short */
tSocketServerAddr.sin_addr.s_addr = htonl(INADDR_ANY); /* 绑定到本机所有网络接口 */
bzero(tSocketServerAddr.sin_zero, 8);
iRet = bind(iSocketSever, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));
if (iRet == -1)
{
printf("bind error\n");
return -1;
}
/* 3. 开启socket监听模式
* 建立服务请求队列,BACKLOG为最大连接数
*/
iRet = listen(iSocketSever, BACKLOG);
if (iRet == -1)
{
printf("listen error\n");
return -1;
}
while (1)
{
/* 4. 休眠,等待建立连接
* 客户端地址存放在第二个参数中
* 以后用新的socket描述符进行读写操作
*/
iAddrLen = sizeof(struct sockaddr);
iNewSocketSever = accept(iSocketSever, (struct sockaddr *)&tSocketClientAddr, &iAddrLen);
if (iNewSocketSever != -1)
{
/* 成功建立连接 */
printf("Get connect from IP:%s, port:%d\n", inet_ntoa(tSocketClientAddr.sin_addr), ntohs(tSocketClientAddr.sin_port)); /* net to ascII */
/* fork系统调用创建子进程 */
if (fork() == 0)
{
/* 子进程源码 */
while (1)
{
/* recv函数接收客户端数据并显示,如果flags为0,则和read、write一样的操作 */
iRecvLen = recv(iNewSocketSever, ucRecvBuf, 999, 0); /* flags一般情况下置为0 */
if (iRecvLen <= 0)
{
/* recv出错(-1)或连接关闭(0) */
close(iNewSocketSever);
printf("Disconnected!\n");
return -1;
}
else
{
ucRecvBuf[iRecvLen] = '\0';
printf("The recv msg is : %s\n", ucRecvBuf);
}
}
}
else
{
/* 父进程代码 */
close(iNewSocketSever); /* 父进程关闭已连接描述符尤为重要 */
}
}
}
close(iSocketSever);
return 0;
}
注意,如果使用了SOCK_STREAM套接字,就不能保证一次能够接受到整个数据。一般情况是要在循环中反复读,直到返回0.
UDP客户端:
/* 基于UDP协议的socket编程客户端代码 */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
/* 客户端:
* socket-->connect-->send/recv
*/
#define SERVER_PORT 8888
int main(int argc, char **argv)
{
int iRet;
int iSocketClient;
struct sockaddr_in tSocketServerAddr;
int iSendLen;
unsigned char ucSendBuf[1000];
if (argc != 2)
{
printf("Usage:\n");
printf("%s <server_ip>\n", argv[0]);
return -1;
}
/* 1. 建立socket */
iSocketClient = socket(AF_INET, SOCK_DGRAM, 0);
/* 2. 不会建立真正的连接,只是让socket描述符中带有服务器地址,
* 如果不使用connect函数,则应该使用sento函数发送数据
*/
tSocketServerAddr.sin_family = AF_INET; /* recommend */
tSocketServerAddr.sin_port = htons(SERVER_PORT); /* host to net short */
if (inet_aton(argv[1], &tSocketServerAddr.sin_addr) == 0) /* 字符串转服务器IP */
{
printf("Invalid server IP\n");
return -1;
}
bzero(tSocketServerAddr.sin_zero, 8);
/* UDP中不需要建立连接,但这里使用connect是可以将地址绑定到iSocketClient上,send函数中就不需要地址信息了 */
iRet = connect(iSocketClient, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));
if (iRet == -1)
{
printf("connect error\n");
return -1;
}
while (1)
{
if (fgets(ucSendBuf, 999, stdin))
{
/* send函数发送数据 */
iSendLen = send(iSocketClient, ucSendBuf, strlen(ucSendBuf), 0);
if (iSendLen == -1)
{
/* 函数出错 */
close(iSocketClient);
return -1;
}
}
}
return 0;
}
UDP服务端:
/* 基于UDP协议的socket编程服务器端代码 */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
/* 服务器:
* socket-->bind-->sendto/recvfrom
*/
#define SERVER_PORT 8888
int main(int argc, char **argv)
{
int iRet;
int iAddrLen;
int iSocketSever;
struct sockaddr_in tSocketServerAddr;
struct sockaddr_in tSocketClientAddr;
int iRecvLen;
unsigned char ucRecvBuf[1000];
/* 1. 建立socket
* AF_INET: IPv4 Internet protocols
* SOCK_DGRAM: UDP协议
* 0: 通常赋值
*/
iSocketSever = socket(AF_INET, SOCK_DGRAM, 0);
if (iSocketSever == -1)
{
printf("socket error\n");
return -1;
}
/* 2. 配置socket
* 设置监听本机的哪个IP,端口
*/
tSocketServerAddr.sin_family = AF_INET; /* recommend */
tSocketServerAddr.sin_port = htons(SERVER_PORT); /* host to net short */
tSocketServerAddr.sin_addr.s_addr = htonl(INADDR_ANY); /* 填入本机所有IP */
bzero(tSocketServerAddr.sin_zero, 8);
iRet = bind(iSocketSever, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));
if (iRet == -1)
{
printf("bind error\n");
return -1;
}
while (1)
{
/* 接收客户端数据,地址信息放在tSocketClientAddr结构体中 */
iAddrLen = sizeof(struct sockaddr);
iRecvLen = recvfrom(iSocketSever, ucRecvBuf, 999, 0, (struct sockaddr *)&tSocketClientAddr, &iAddrLen);
if (iRecvLen > 0)
{
ucRecvBuf[iRecvLen] = '\0';
printf("Get msg from %s : %s\n", inet_ntoa(tSocketClientAddr.sin_addr), ucRecvBuf);
}
}
close(iSocketSever);
return 0;
}
UDP客户端(无connect函数):
/* 基于UDP协议的socket编程客户端代码 */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
/* 客户端:
* socket-->sendto/recvfrom
*/
#define SERVER_PORT 8888
int main(int argc, char **argv)
{
int iRet;
int iSocketClient;
struct sockaddr_in tSocketServerAddr;
int iSendLen;
unsigned char ucSendBuf[1000];
if (argc != 2)
{
printf("Usage:\n");
printf("%s <server_ip>\n", argv[0]);
return -1;
}
/* 1. 建立socket */
iSocketClient = socket(AF_INET, SOCK_DGRAM, 0);
/* 2. 设置服务器地址 */
tSocketServerAddr.sin_family = htonl(AF_INET); /* recommend */
tSocketServerAddr.sin_port = htons(SERVER_PORT); /* host to net short */
if (inet_aton(argv[1], &tSocketServerAddr.sin_addr) == 0) /* 字符串转服务器IP */
{
printf("Invalid server IP\n");
return -1;
}
bzero(tSocketServerAddr.sin_zero, 8);
while (1)
{
if (fgets(ucSendBuf, 999, stdin))
{
/* sendto函数发送数据 */
iSendLen = sendto(iSocketClient, ucSendBuf, strlen(ucSendBuf), 0,
(const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));
if (iSendLen == -1)
{
/* 函数出错 */
close(iSocketClient);
return -1;
}
}
}
return 0;
}
UDP服务端(无connect函数):
/* 基于UDP协议的socket编程服务器端代码 */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
/* 服务器:
* socket-->bind-->sendto/recvfrom
*/
#define SERVER_PORT 8888
int main(int argc, char **argv)
{
int iRet;
int iAddrLen;
int iSocketSever;
struct sockaddr_in tSocketServerAddr;
struct sockaddr_in tSocketClientAddr;
int iRecvLen;
unsigned char ucRecvBuf[1000];
/* 1. 建立socket
* AF_INET: IPv4 Internet protocols
* SOCK_DGRAM: UDP协议
* 0: 通常赋值
*/
iSocketSever = socket(AF_INET, SOCK_DGRAM, 0);
if (iSocketSever == -1)
{
printf("socket error\n");
return -1;
}
/* 2. 配置socket
* 设置监听本机的哪个IP,端口
*/
tSocketServerAddr.sin_family = AF_INET; /* recommend */
tSocketServerAddr.sin_port = htons(SERVER_PORT); /* host to net short */
tSocketServerAddr.sin_addr.s_addr = INADDR_ANY; /* 填入本机所有IP */
bzero(tSocketServerAddr.sin_zero, 8);
iRet = bind(iSocketSever, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));
if (iRet == -1)
{
printf("bind error\n");
return -1;
}
while (1)
{
/* 接收客户端数据,地址信息放在tSocketClientAddr结构体中 */
iAddrLen = sizeof(struct sockaddr);
iRecvLen = recvfrom(iSocketSever, ucRecvBuf, 999, 0, (struct sockaddr *)&tSocketClientAddr, &iAddrLen);
if (iRecvLen > 0)
{
ucRecvBuf[iRecvLen] = '\0';
printf("Get msg from %s : %s\n", inet_ntoa(tSocketClientAddr.sin_addr), ucRecvBuf);
}
}
close(iSocketSever);
return 0;
}
参考:
《unix环境高级编程》第16章。