一、套接字
套接字:源IP地址和目的IP地址以及源端口号和目的端口号的组合称为套接字。其用于标识客户端请求的服务器和服务。
常用的TCP/IP协议的3种套接字类型如下所示:
- 流套接字(SOCK_STREAM):
流套接字用于提供面向连接、可靠的数据传输服务。该服务将保证数据能够实现无差错、无重复发送,并按顺序接收。流套接字之所以能够实现可靠的数据服务,原因在于其使用了传输控制协议,即TCP(The Transmission ControlProtocol)协议。
- 数据报套接字(SOCK_DGRAM):
数据报套接字提供了一种无连接的服务。该服务并不能保证数据传输的可靠性,数据有可能在传输过程中丢失或出现数据重复,且无法保证顺序地接收到数据。数据报套接字使用UDP(User Datagram Protocol)协议进行数据的传输。
由于数据报套接字不能保证数据传输的可靠性,对于有可能出现的数据丢失情况,需要在程序中做相应的处理。- 原始套接字(SOCK_RAW):
原始套接字(SOCKET_RAW)允许对较低层次的协议直接访问,比如IP、 ICMP协议,它常用于检验新的协议实现,或者访问现有服务中配置的新设备,因为RAW SOCKET可以自如地控制Windows下的多种协议,能够对网络底层的传输机制进行控制,所以可以应用原始套接字来操纵网络层和传输层应用。比如,我们可以通过RAW SOCKET来接收发向本机的ICMP、IGMP协议包,或者接收TCP/IP栈不能够处理的IP包,也可以用来发送一些自定包头或自定协议的IP包。网络监听技术很大程度上依赖于SOCKET_RAW。
二、相关函数、结构体
2.1 套接字函数
套接字函数 | 描述 |
---|---|
Linux: int socket(int af, int type, int protocol); Windows: SOCKET socket(int af, int type, int protocol); | 创建套接字 |
Linux: int bind(int sock, const struct sockaddr *myaddr, socklen_t addrlen); Windows: int bind(SOCKET sock, const struct sockaddr *addr, int addrlen); | 绑定套接字 |
Linux: int listen(int sockfd, int backlog); Windows: int listen(SOCKET sock, int backlog); | 进入监听状态 |
Linux: int accept(int sockfd, struct sockaddr *client_addr, socklen_t *len); Windows: SOCKET accept(SOCKET sock, struct sockaddr *addr, int *addrlen); | 等待客户端连接 |
Linux: int connect(int sock_fd, struct sockaddr *serv_addr,int addrlen); Windows: int connect(SOCKET sock, const struct sockaddr *serv_addr, int addrlen); | 客户端连接服务器 |
2.2 接受、发送函数
函数 | 描述 |
---|---|
Linux: ssize_t write(int fd, const void *buf, size_t nbytes); Windows: int send(SOCKET sock, const char *buf, int len, int flags); | 发送数据 |
Linux: ssize_t read(int fd, void *buf, size_t nbytes); Windows: int recv(SOCKET sock, char *buf, int len, int flags); | 读取数据 |
2.3 相关结构体
结构体 | 描述 |
---|---|
Linux: struct sockaddr_in Windows: SOCKADDR_IN | 设置IP地址,端口等信息 |
Linux: struct sockaddr Windows: SOCKET | 设置IP地址,端口等信息 |
2.4 IP、端口函数
函数 | 描述 |
---|---|
* int inet_pton(int family, const char *strptr, void *addrptr); | 将IP地址在“点分十进制”和“二进制整数”之间转换。而且,能够处理ipv4和ipv6 |
* const char * inet_ntop(int family, const void *addrptr, char *strptr, size_t len); | 将数值格式转化为点分十进制的ip地址格式。而且,能够处理ipv4和ipv6 |
u_short htons(u_short hostshort); | 将一个无符号短整型的主机数值转换为网络字节顺序,即大尾顺序(big-endian) |
ntohs(): | network to host short,将short类型数据从网络字节序转换为主机字节序。 |
htonl(): | host to network long,将long类型数据从主机字节序转换为网络字节序。 |
ntohl(): | network to host long,将long类型数据从网络字节序转换为主机字节序。 |
三、Windows示列
服务端代码
#include <stdio.h>
#include <WinSock2.h>
#pragma comment (lib, "ws2_32.lib") //加载 ws2_32.lib
#define BUF_SIZE 100
int main() {
//
WSADATA wsaData;
WSAStartup(MAKEWORD(2,2), &wsaData);
// 创建套接字
SOCKET serverSock = socket(AF_INET, SOCK_STREAM, 0);
// 绑定套接字
SOCKADDR_IN sockAddr;
memset(&sockAddr, 0, sizeof(sockAddr));
sockAddr.sin_family = PF_INET;
sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
sockAddr.sin_port = htons(9080);
bind(serverSock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR));
// 进入监听状态
listen(serverSock, 20);
// 接受客户端请求
SOCKADDR cltAddr;
int nSize = sizeof(cltAddr);
char buffer[BUF_SIZE] = {0};
while(1) {
// 阻塞直到客户端发来消息
SOCKET clientSock = accept(serverSock, (SOCKADDR*)&cltAddr, &nSize);
// 接受客户端消息
int strLen = recv(clientSock, buffer, BUF_SIZE, 0);
printf("Message form client: %s\n", buffer);
// 给客户端回复消息
printf("Input a string: \n");
gets_s(buffer, BUF_SIZE);
send(clientSock, buffer, strLen, 0);
// 关闭套接字
closesocket(clientSock);
// 重置缓冲区
memset(buffer, 0, BUF_SIZE);
}
// 关闭套接字
closesocket(serverSock);
// 终止DLL使用
WSACleanup();
}
客户端代码
#include <stdio.h>
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib") //加载 ws2_32.lib
#define BUF_SIZE 100
int main(){
//初始化DLL
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
// 创建套接字
SOCKADDR_IN sockAddr;
memset(&sockAddr, 0, sizeof(sockAddr));
sockAddr.sin_family = PF_INET;
sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
sockAddr.sin_port = htons(9080);
char bufSend[BUF_SIZE] = {0};
char bufRecv[BUF_SIZE] = {0};
while (1) {
// 创建套接字
SOCKET sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
// 连接服务器
connect(sock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR));
// 获取用户输入的字符串并发送给服务器
printf("Input a string:");
gets_s(bufSend, sizeof(bufSend));
send(sock, bufSend, BUF_SIZE, 0);
// 接受服务器传回来的消息
recv(sock, bufRecv, BUF_SIZE, 0);
printf("Message form server: %s\n", bufRecv);
// 重置缓冲区
memset(bufSend, 0, BUF_SIZE);
memset(bufRecv, 0, BUF_SIZE);
// 关闭套接字
closesocket(sock);
}
// 终止DLL使用
WSACleanup();
return 0;
}
四、Linux示列
服务端
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define BUF_SIZE 100
int main() {
// 创建套接字
int serverSock = socket(AF_INET, SOCK_STREAM, 0);
// 绑定套接字
struct sockaddr_in sockAddr;
memset(&sockAddr, 0, sizeof(sockAddr));
sockAddr.sin_family = PF_INET;
sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
sockAddr.sin_port = htons(9080);
bind(serverSock, (struct sockaddr*)&sockAddr, sizeof(sockAddr));
// 进入监听状态
listen(serverSock, 20);
// 接受客户端请求
struct sockaddr cltAddr;
socklen_t nSize = sizeof(cltAddr);
char buffer[BUF_SIZE] = {0};
while(1) {
// 阻塞直到客户端发来消息
int clientSock = accept(serverSock, (struct sockaddr*)&cltAddr, &nSize);
// 接受客户端消息
int strLen = read(clientSock, buffer, BUF_SIZE);
printf("Message form client: %s\n", buffer);
// 给客户端回复消息
printf("Input a string: \n");
fgets(buffer, sizeof(buffer), stdin);
write(clientSock, buffer, strLen);
// 关闭套接字
close(clientSock);
// 重置缓冲区
memset(buffer, 0, BUF_SIZE);
}
// 关闭套接字
close(serverSock);
}
客户端
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define BUF_SIZE 100
int main(){
// 创建套接字
struct sockaddr_in sockAddr;
memset(&sockAddr, 0, sizeof(sockAddr));
sockAddr.sin_family = AF_INET;
sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
sockAddr.sin_port = htons(9080);
char bufSend[BUF_SIZE] = {0};
char bufRecv[BUF_SIZE] = {0};
while (1) {
// 创建套接字
int sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
// 连接服务器
connect(sock, (struct sockaddr*)&sockAddr, sizeof(sockAddr));
// 获取用户输入的字符串并发送给服务器
printf("Input a string:");
fgets(bufSend, sizeof(bufSend), stdin);
send(sock, bufSend, BUF_SIZE, 0);
// 接受服务器传回来的消息
recv(sock, bufRecv, BUF_SIZE, 0);
printf("Message form server: %s\n", bufRecv);
// 重置缓冲区
memset(bufSend, 0, BUF_SIZE);
memset(bufRecv, 0, BUF_SIZE);
// 关闭套接字
close(sock);
}
return 0;
}