【Socket】套接字编程
语法
一、套接字及创建
1. 什么是套接字?
套接字是一种通信机制(通信的两方的一种约定),socket屏蔽了各个协议的通信细节,提供了tcp/ip协议的抽象,对外提供了一套接口,同过这个接口就可以统一、方便的使用tcp/ip协议的功能。这使得程序员无需关注协议本身,直接使用socket提供的接口来进行互联的不同主机间的进程的通信。我们可以用套接字中的相关函数来完成通信过程。
套接字的特性有三个属性确定,它们是:域(domain),类型(type),和协议(protocol)。
-
域:指定套接字通信中使用的网络介质。最常见的套接字域是 AF_INET(IPv4)或者AF_INET6(IPV6),它是指 Internet 网络。
-
类型:
流套接字(SOCK_STREAM): 流套接字用于提供面向连接、可靠的数据传输服务。该服务将保证数据能够实现无差错、无重复发送,并按顺序接收。流套接字之所以能够实现可靠的数据服务,原因在于其使用了传输控制协议,即TCP ;
数据报套接字(SOCK_DGRAM): 数据报套接字提供了一种无连接的服务。该服务并不能保证数据传输的可靠性,数据有可能在传输过程中丢失或出现数据重复,且无法保证顺序地接收到数据。数据报套接字使用UDP(User Datagram Protocol)协议进行数据的传输;
原始套接字(SOCK_RAW): 原始套接字与标准套接字(标准套接字指的是前面介绍的流套接字和数据报套接字)的区别在于:原始套接字可以读写内核没有处理的IP数据包,而流套接字只能读取TCP协议的数据,数据报套接字只能读取UDP协议的数据。因此,如果要访问其他协议发送数据必须使用原始套接字。
- 协议:IPPROTO_TCP,IPPROTO_UDP
2. 创建套接字
int sockfd = socket(domain, type, protocol)
- sockfd:套接字描述符,一个整数(如文件句柄)
- domain:整数,通信域,例如AF_INET(IPv4协议),AF_INET6(IPv6协议)
- type:通信类型 SOCK_STREAM:TCP(可靠,面向连接) SOCK_DGRAM:UDP(不可靠,无连接的)
- protocol: Internet协议(IP)的协议值,为0。这与出现在数据包IP报头的协议字段中的数字相同。(有关详细信息,请参见手动协议)
二、端口绑定
套接字作为一层抽象,可以说是端口的代理人,主机用户只需要和本套接字进行交互,而不在意套接字的具体实现过程。因此,套接字创建完毕后,要和端口进行绑定,之后的信息流进流出端口,其复杂的过程就被抽象为与套接字这个代理人的交互过程了。
自然要知道绑定的端口的地址,可以通过如下设置
struct sockaddr_in client_in;
client_in.sin_port =htons(20001);//端口号20001
client_in.sin_addr.S_un.S_addr = inet_addr("10.128.18.146");//ipv4地址
client_in.sin_family =AF_INET;//选择ipv4协议簇
这个绑定过程,我们通过bind()来实现。
bind():
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
首先如上设置IP/Port信息,接下来将创建的套接字与之进行绑定
//part3 将用户端的socket和用户端的ip地址和端口号绑定
if (bind(socket_client, (struct sockaddr *)&client_in, sizeof(client_in)) == SOCKET_ERROR)
{
printf("blind() Failed:%d\\n", WSAGetLastError());
return USER_ERROR;
}
三、收发信息
1. read() 和write()
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
成功返回读取的字节数,出错返回-1并设置errno,如果在调read之前已到达文件末尾,则这次read返回0。
ssize_t write(int fd, const void *buf, size_t count);
如果顺利write()会返回实际写入的字节数。当有错误发生时则返回-1,错误代码存入errno中。
流字节套接字(例如TCP套接字)上的read和write函数所表现的行为不同于通常的文件I/O。字节流套接字上调用read或write输入或输出的字节数可能比请求的数量少(我们称之为部分读和部分写),然而这不是出错的状态。这个现象的原因在于内核中用于套接字的缓冲区可能已达到了极限。此时所需要的是调用者再次调用read或write函数,以输入或输出剩余的字节
2. recvfrom()和sendto()
recvfrom()从(已连接)套接口上接收数据,并捕获数据发送源的地址。对于SOCK_STREAM类型的套接口,最多可接收缓冲区大小个数据。对于数据报类套接口,队列中第一个数据报中的数据被解包,但最多不超过缓冲区的大小。如果数据报大于缓冲区,那么缓冲区中只有数据报的前面部分,其他的数据都丢失了,并且recvfrom()函数返回WSAEMSGSIZE错误。
SendTo指向一指定目的地发送数据,sendto()适用于发送(未建立连接)的UDP数据包 (参数为SOCK_DGRAM)。
int recvfrom(SOCKET s,void *buf,int len,unsigned int flags, struct sockaddr *from,int *fromlen);
参数:
s: 标识一个已连接套接口的描述字。
buf: 接收数据缓冲区。
len: 缓冲区长度。
flags: 调用操作方式。
from: (可选)指针,指向装有源地址的缓冲区。
fromlen:(可选)指针,指向from缓冲区长度值。
若无错误发生,recvfrom()返回读入的字节数。如果连接已中止,返回0。否则的话,返回SOCKET_ERROR错误,应用程序可通过WSAGetLastError()获取相应错误代码。
int sendto (IN SOCKET s, IN const char FAR * buf, IN int len, IN int flags, IN const struct sockaddr FAR *to, IN int tolen);
参数:
s 套接字
buff 待发送数据的缓冲区
size 缓冲区长度
Flags 调用方式标志位, 一般为0, 改变Flags,将会改变Sendto发送的形式
addr (可选)指针,指向目的套接字的地址
len addr 所指地址的长度
返回值为整型,如果成功,则返回发送的字节数,失败则返回SOCKET_ERROR。
与recv()函数的比较:
UDP使用recvfrom()函数接收数据,他类似于标准的read(),但是在recvfrom()函数中要指明目的地址。从套接字上接收一个消息。对于recvfrom ,可同时应用于面向连接的和无连接的套接字。recv一般只用在面向连接的套接字,几乎等同于recvfrom,只要将recvfrom的第五个参数设置NULL。不管是recv还是recvfrom,都有两种模式,阻塞和非阻塞,可以通过ioctl函数来设置。阻塞模式是一直等待直到有数据到达,非阻塞模式是立即返回,需要通过消息,异步事件等来查询完成状态。
recv函数
int recv( SOCKET s, char *buf, int len, int flags)
参数一:指定接收端套接字描述符;
参数二:指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;
参数三:指明buf的长度;
参数四 :一般置为0。
功能:不论是客户还是服务器应用程序都用recv函数从TCP连接的另一端接收数据。
与send()函数的比较:
是向一个已经连接的socket发送数据,而sendto则是面向未建立连接的UDP数据报。不论是客户端还是服务器应用程序都用send函数来向TCP连接的另一端发送数据。客户程序一般用send函数向服务器发送请求,而服务器则通常用send函数来向客户程序发送应答。
send函数
int send( SOCKET s,char *buf,int len,int flags );
参数一:指定发送端套接字描述符;
参数二:存放应用程序要发送数据的缓冲区;
参数三:实际要发送的数据的字节数;
参数四:一般置为0。
功能:不论是客户还是服务器应用程序都用send函数来向TCP连接的另一端发送数据。客户程序一般用send函数向服务器发送请求,而服务器则通常用send函数来向客户程序发送应答。
实例
1. TCP通信的简单实现
TCP_Server.cpp
//
// Created by AirCL on 2024/8/12.
//
#include <iostream>
#include <winsock2.h>
#include <ctime>
#include <cstring>
#pragma warning(disable: 4996)
#pragma comment(lib, "ws2_32.lib")
#define USER_ERROR -1
using namespace std;
void space2_(char* str){
for(unsigned int i = 0; i < strlen(str); i++){
if (str[i] == ' '){
str[i] = '_';
}
}
}
void printTime(){
time_t curTime;
time(&curTime);
char* now = ctime(&curTime);
cout << "[" << now << "]" << endl;
}
void printTime(in_addr addr){
time_t curTime;
time(&curTime);
char* now = ctime(&curTime);
cout << "[" << now << "](From " << inet_ntoa(addr) << ")" << endl;
}
void interactive(SOCKET socketOfClient, struct sockaddr_in cSin){
char recvData[200];
char sendData[200];
printTime();
cout << "接收到一个连接:" << inet_ntoa(cSin.sin_addr) << endl;
while (true){
// 读取消息
int ret = recv(socketOfClient, recvData, 200, 0);
if (ret < 0){
printTime();
cout << "Something wrong!" << endl;
continue;
}
if (!strcmp(recvData, "quit")){
break;
}
printTime(cSin.sin_addr);
cout << "读取消息:" << recvData << endl;
cout << "请发送消息:";
gets(sendData);
printTime();
cout << "发送消息:" << sendData << endl;
send(socketOfClient, sendData, 199, 0);
if (!strcmp(sendData, "quit")){
break;
}
}
}
int main(){
WSADATA wsaData;
// 打开网络库、启动网络库,启动了这个库,这个库里的函数才能使用
if (WSAStartup(MAKEWORD(2, 2), &wsaData)){
cout << "Failed to load WinSock" << endl;
return USER_ERROR;
} else{
cout << "Load WinSock success" << endl;
}
// 创建socket
SOCKET socketOfServer;
SOCKET socketOfClient;
socketOfServer = socket(AF_INET, SOCK_STREAM, 0);
// 确保socket创建成功
if (socketOfServer == INVALID_SOCKET){
cout << "socket() Failed: " << WSAGetLastError() << endl;
return USER_ERROR;
} else{
cout << "socket() Success" << endl;
}
// 填写套接字信息
struct sockaddr_in sSin;
memset(&sSin, 0, sizeof(sockaddr_in));
sSin.sin_family = AF_INET;
sSin.sin_port = htons(20000);
sSin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
//将服务器的socket和服务器的ip地址和端口绑定
if (bind(socketOfServer, (struct sockaddr*)& sSin, sizeof(sSin)) == SOCKET_ERROR){
cout << "bind() Failed: " << WSAGetLastError() << endl;
return USER_ERROR;
} else{
cout << "bind() Success" << endl;
}
// 启动监听
if (listen(socketOfServer, 3) == SOCKET_ERROR){
printTime();
cout << "listen() Failed: " << WSAGetLastError() << endl;
return USER_ERROR;
} else{
cout << "listen() Success" << endl;
}
struct sockaddr_in cSin;
int cSinLen = sizeof(struct sockaddr_in);
// 不断监听,直到接收到客户端的连接
cout << "================Waiting for connect .. ==================" << endl;
while (true){
// 建立tcp连接
socketOfClient = accept(socketOfServer, (struct sockaddr *)& cSin, &cSinLen);
if (socketOfClient == INVALID_SOCKET){
cout << "accept() Failed: " << WSAGetLastError() << endl;
printTime();
} else{
interactive(socketOfClient, cSin);
}
}
closesocket(socketOfServer);
WSACleanup();
return 0;
}
TCP_Client.cpp
//
// Created by AirCL on 2024/8/12.
//
#include <iostream>
#include <winsock2.h>
#include <time.h>
#include <cstring>
#include <direct.h>
#pragma warning(disable: 4996)
#pragma comment(lib, "ws2_32.lib")
#define USER_ERROR -1
using namespace std;
void printTime(){
time_t curTime;
time(&curTime);
char* now = ctime(&curTime);
cout << "[" << now << "]" << endl;
}
void printTimeFrom(in_addr addr){
time_t curTime;
time(&curTime);
char* now = ctime(&curTime);
cout << "[" << now << "](From " << inet_ntoa(addr) << endl;
}
void printTimeTo(in_addr addr){
time_t curTime;
time(&curTime);
char* now = ctime(&curTime);
cout << "[" << now << "](To " << inet_ntoa(addr) << endl;
}
int main(){
char recvData[200];
char sendData[200];
int ret;
// 启动套接字编程,不启动无法运行相关API
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData)){
cout << "Failed to load WinSock" << endl;
return USER_ERROR;
} else{
cout << "Load WinSock Success" << endl;
}
// 创建套接字
SOCKET socketOfClient = socket(AF_INET, SOCK_STREAM, 0);
if (socketOfClient == INVALID_SOCKET){
cout << "Failed socket()" << endl;
return USER_ERROR;
} else{
cout << "Socket() Success" << endl;
}
// 设置套接字信息
struct sockaddr_in serverIn;
serverIn.sin_family = AF_INET;
serverIn.sin_port = htons(20000);
serverIn.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
cout << "===================Trying to connect...====================" << endl;
// 连接服务器
if (connect(socketOfClient, (struct sockaddr*)&serverIn, sizeof(serverIn)) == -1){
cout << "Failed to connect()" << endl;
return USER_ERROR;
} else{
printTime();
cout << "connect to " << inet_ntoa(serverIn.sin_addr) << ":" << serverIn.sin_port << endl;
while (true){
memset(recvData, 0, sizeof(recvData));
memset(sendData, 0, sizeof(sendData));
cout << "请发送消息:" << endl;
gets(sendData);
printTimeTo(serverIn.sin_addr);
cout << "发送消息:" << sendData << endl;
send(socketOfClient, sendData, strlen(sendData), 0);
if (!strcmp(sendData, "quit")){
break;
}
// 接受消息
ret = recv(socketOfClient, recvData, 200, 0);
printTimeFrom(serverIn.sin_addr);
cout << "读取消息:" << recvData << endl;
}
}
closesocket(socketOfClient);
WSACleanup();
return 0;
}
我们实现了两台机子的互相通信,能不能实现多个机子之间的互相通信呢?仔细想想,这就涉及到同时干多件事情的操作了。因此需要开多线程进行程序的处理。
TCP Server.cpp (多线程)
//server.cpp
#include <stdio.h>
#include <Winsock2.h>
#include <time.h>
#include <string.h>
#include <thread>
#include <iostream>
#pragma warning(disable : 4996)
#pragma comment(lib, "ws2_32.lib")
#define USER_ERROR -1
using namespace std;
void space2_(char* str);
void print_time();
void print_time_from(in_addr addr);
void print_time_to(in_addr addr);
bool GetAddressBySocket(SOCKET m_socket, SOCKADDR_IN& m_address);
void delete_last_line();
//用于交互
DWORD WINAPI interactive(LPVOID lpThreadParameter);
int main()
{
WSADATA wsaData;
// 打开网络库/启动网络库,启动了这个库,这个库里的函数/功能才能使用
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
printf("Failed to load Winsock.\\n");
return USER_ERROR;
}
SOCKET socket_of_server;
SOCKET socket_of_client;
//part1 创建socket
socket_of_server = socket(AF_INET, SOCK_STREAM, 0);
//part1 end
// 确保socker创建成功
if (socket_of_server == INVALID_SOCKET)
{
printf("socket() Failed:%d\\n", WSAGetLastError());
return USER_ERROR;
}
//part2 填写套接字信息
struct sockaddr_in s_sin;
s_sin.sin_family = AF_INET;
s_sin.sin_port = htons(20000);
s_sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
//part2 end
//part3 将服务器的socket和服务器的ip地址和端口号绑定
if (bind(socket_of_server, (struct sockaddr*)&s_sin, sizeof(s_sin)) == SOCKET_ERROR)
{
printf("blind() Failed:%d\\n", WSAGetLastError());
return USER_ERROR;
}
//part4 启动监听
if (listen(socket_of_server, 3) == SOCKET_ERROR)
//part4 end
{
print_time();
printf("listen() Failed:%d\\n", WSAGetLastError());
return USER_ERROR;
}
struct sockaddr_in c_sin;
int c_sin_len = sizeof(struct sockaddr_in);
// 不断监听,直到接收到客户端的连接
printf("==========Waiting for connect...==========\\n");
while (1)
{
//part5 建立tcp连接
socket_of_client = accept(socket_of_server, (struct sockaddr*)&c_sin, &c_sin_len);
//part5 end
if (socket_of_client == INVALID_SOCKET)
{
printf("accept() Failed:%d", WSAGetLastError());
print_time();
}
else
{
HANDLE hThread = CreateThread(NULL, 0, interactive, (LPVOID)socket_of_client, 0,NULL);
}
}
closesocket(socket_of_server);
WSACleanup();
return 0;
}
void space2_(char* str)
{
for (auto i = 0; i < strlen(str); i++)
{
if (str[i] == ' ')
{
str[i] = '_';
}
}
}
void print_time() {
time_t cur_time;
time(&cur_time);
char* now = ctime(&cur_time);
now[24] = '\\0';
printf("[%s]", now);
}
void print_time_from(in_addr addr) {
time_t cur_time;
time(&cur_time);
char* now = ctime(&cur_time);
now[24] = '\\0';
printf("[%s](From %s):", now, inet_ntoa(addr));
}
void print_time_to(in_addr addr) {
time_t cur_time;
time(&cur_time);
char* now = ctime(&cur_time);
now[24] = '\\0';
printf("[%s](To %s):", now, inet_ntoa(addr));
}
DWORD WINAPI interactive(LPVOID lpThreadParameter)
{
char recvData[200];
char sendData[200];
SOCKADDR_IN c_sin;
print_time();
SOCKET socket_of_client = (SOCKET)lpThreadParameter;
GetAddressBySocket(socket_of_client,c_sin);
printf("接收到一个连接:IP(%s)Port(%d)\\r\\n", inet_ntoa(c_sin.sin_addr), ntohs(c_sin.sin_port));
while (1)
{
recvData[0] = '\\0';
//part6 读取消息
int ret = recv(socket_of_client, recvData, 199, 0);
//part6 end
if (ret < 0)
{
//m.lock();
print_time();
printf("***Something wrong***\\n");
//m.unlock();
continue;
}
recvData[ret] = '\\0';
// 如果客户端发送了quit,那么就退出
if (strcmp(recvData, "quit") == 0)
break;
//m.lock();
print_time_from(c_sin.sin_addr);
printf("读取消息:【%s】\\n",recvData);
//printf("%s\\n", recvData);
printf("请发送消息:");
gets_s(sendData);
// space2_(sendData);
delete_last_line();
print_time_to(c_sin.sin_addr);
printf("发送消息:【%s】\\n", sendData);
//printf("\\n");
//m.unlock();
//part7 发送消息
send(socket_of_client, sendData, 199, 0);
//part7 end
if (strcmp("quit", sendData) == 0)
break;
}
}
//通过套接字获取IP、Port等地址信息
bool GetAddressBySocket(SOCKET m_socket, SOCKADDR_IN& m_address)
{
memset(&m_address, 0,sizeof(m_address));
int nAddrLen = sizeof(m_address);
//根据套接字获取地址信息
if (::getpeername(m_socket, (SOCKADDR*)&m_address, &nAddrLen) != 0)
{
//printf("Get IP address by socket Failed!n");
return false;
}
//读取IP和Port
//cout << "IP: " << ::inet_ntoa(m_address.sin_addr) << " PORT: " << ntohs(m_address.sin_port) << endl;
return true;
}
void delete_last_line()
{
printf("\\033[1A"); //先回到上一行
printf("\\033[K"); //清除该行
printf("\\r \\r");
}
2. UDP通信的简单实现
UDP_Server.cpp
//UDP_server.cpp
#include <stdio.h>
#include <Winsock2.h>
#include <time.h>
#include <string.h>
#pragma warning(disable : 4996)
#pragma comment(lib, "ws2_32.lib")
#define USER_ERROR -1
using namespace std;
void space2_(char *str);
void print_time();
void print_time(in_addr addr);
int main()
{
char recvData[200];
char sendData[200];
WSADATA wsaData;
// 打开网络库/启动网络库,启动了这个库,这个库里的函数/功能才能使用
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
printf("Failed to load Winsock.\\n");
return USER_ERROR;
}
SOCKET socket_of_server;
//part1 创建socket
socket_of_server = socket(AF_INET, SOCK_DGRAM, 0);
//part1 end
// 确保socker创建成功
if (socket_of_server == INVALID_SOCKET)
{
printf("socket() Failed:%d\\n", WSAGetLastError());
return USER_ERROR;
}
//part2 填写套接字信息
struct sockaddr_in server_in;
server_in.sin_family = AF_INET;
server_in.sin_port = htons(20000);
server_in.sin_addr.S_un.S_addr =inet_addr("10.129.46.156");
//part2 end
//part3 将服务器的socket和服务器的ip地址和端口号绑定
if (bind(socket_of_server, (struct sockaddr *)&server_in, sizeof(server_in)) == SOCKET_ERROR)
{
printf("blind() Failed:%d\\n", WSAGetLastError());
return USER_ERROR;
}
struct sockaddr_in dest_addr;
int len = sizeof(struct sockaddr_in);
printf("——————————聊天室已经启动——————————\\n");
while (1)
{
memset(recvData,'\\0',sizeof(recvData));
memset(sendData,'\\0',sizeof(sendData));
recvfrom(socket_of_server,recvData,200,0,(struct sockaddr *)&dest_addr,&len);
print_time(dest_addr.sin_addr);
printf("收到消息:%s\\n",recvData);
printf("输入要传输的信息:");
gets(sendData);
printf("\\033[1A"); //先回到上一行
printf("\\033[K"); //清除该行
print_time();
printf("发送消息:%s\\n",sendData);
sendto(socket_of_server,sendData,strlen(sendData),0,(struct sockaddr *)&dest_addr,len);
}
closesocket(socket_of_server);
WSACleanup();
return 0;
}
void space2_(char *str)
{
for(unsigned int i=0; i<strlen(str); i++)
{
if(str[i] == ' ')
{
str[i] = '_';
}
}
}
void print_time(){
time_t cur_time;
time(&cur_time);
char *now=ctime(&cur_time);
now[24]='\\0';
printf("[%s]",now);
}
void print_time(in_addr addr){
time_t cur_time;
time(&cur_time);
char *now=ctime(&cur_time);
now[24]='\\0';
printf("[%s](From %s):",now,inet_ntoa(addr));
}
UDP_Client.cpp
//UDP_client.cpp
#include <winsock2.h>
#include <time.h>
#include <stdio.h>
#include <string.h>
#include <direct.h>
#include <time.h>
#pragma comment(lib, "ws2_32.lib")
#pragma warning(disable : 4996)
#define USER_ERROR -1
void print_time_from(in_addr addr);
void print_time_to(in_addr addr);
void print_time();
void print_time(in_addr addr);
int main()
{
// time_t now;
// char* curr_time = time(&now);
char recvData[200];
char sendData[200];
//启动套接字编程,不启动无法运行相关API
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
printf("Failed to load Winsock.\\n");
return USER_ERROR;
}
//part1 创建套接字
SOCKET socket_client = socket(AF_INET, SOCK_DGRAM, 0);
//end part1
if (socket_client == INVALID_SOCKET)
{
printf(" Failed socket() \\n");
return 0;
}
//part2 设置套接字信息 这里是需要
struct sockaddr_in client_in;
client_in.sin_port =htons(20001);
client_in.sin_addr.S_un.S_addr = inet_addr("10.128.18.146");
client_in.sin_family =AF_INET;
//part2 end
//part3 将服务器的socket和服务器的ip地址和端口号绑定
if (bind(socket_client, (struct sockaddr *)&client_in, sizeof(client_in)) == SOCKET_ERROR)
{
printf("blind() Failed:%d\\n", WSAGetLastError());
return USER_ERROR;
}
struct sockaddr_in dest_addr;
dest_addr.sin_family = AF_INET;
dest_addr.sin_port = htons(20000);
dest_addr.sin_addr.s_addr = inet_addr("10.129.46.156");
int len = sizeof(dest_addr);
printf("——————————聊天室已经启动——————————\\n");
while(1)
{
memset(recvData,'\\0',sizeof(recvData));
memset(sendData,'\\0',sizeof(sendData));
printf("输入要传输的信息:");
gets(sendData);
printf("\\033[1A"); //先回到上一行
printf("\\033[K"); //清除该行
print_time_to(dest_addr.sin_addr);
printf("发送消息:%s\\n",sendData);
sendto(socket_client,sendData,strlen(sendData),0,(struct sockaddr *)&dest_addr,sizeof(dest_addr));
recvfrom(socket_client,recvData,200,0,(struct sockaddr *)&dest_addr,&len);
print_time_from(dest_addr.sin_addr);
printf("收到消息:%s\\n",recvData);
}
closesocket(socket_client);
WSACleanup();
return 0;
}
void print_time(){
time_t cur_time;
time(&cur_time);
char *now=ctime(&cur_time);
now[24]='\\0';
printf("[%s]",now);
}
void print_time(in_addr addr){
time_t cur_time;
time(&cur_time);
char *now=ctime(&cur_time);
now[24]='\\0';
printf("[%s](From %s):",now,inet_ntoa(addr));
}
void print_time_from(in_addr addr) {
time_t cur_time;
time(&cur_time);
char* now = ctime(&cur_time);
now[24] = '\\0';
printf("[%s](From %s):", now, inet_ntoa(addr));
}
void print_time_to(in_addr addr) {
time_t cur_time;
time(&cur_time);
char* now = ctime(&cur_time);
now[24] = '\\0';
printf("[%s](To %s):", now, inet_ntoa(addr));
}
sockaddr和sockaddr_in详解
struct sockaddr 和 struct sockaddr_in 这两个结构体用来处理网络通信的地址。
1. sockaddr
sockaddr在头文件#include <sys/socket.h>中定义,sockaddr的缺陷是:sa_data把目标地址和端口信息混在一起了,如下:
struct sockaddr
{
unsigned short sa_family;//2字节,地址族,AF_xxx
char sa_data[14]; //14字节,包含套接字中的目标地址和端口信息
};
2. sockaddr_in
sockaddr_in在头文件#include<netinet/in.h>或#include <arpa/inet.h>中定义,该结构体解决了sockaddr的缺陷,把port和addr 分开储存在两个变量中,如下:
struct in_addr {
unsigned long s_addr; // 32位IPV4地址打印的时候可以调用inet_ntoa()函数将其转换为char *类型.
};
struct sockaddr_in {
short sin_family; // 2 字节 ,地址族,e.g. AF_INET, AF_INET6
unsigned short sin_port; // 2 字节 ,16位TCP/UDP 端口号 e.g. htons(3490),
struct in_addr sin_addr; // 4 字节 ,32位IP地址
char sin_zero[8]; // 8 字节 ,不使用
};
sin_port和sin_addr都必须是网络字节序(NBO),一般可视化的数字都是主机字节序(HBO)。
3. 总结
二者长度一样,都是16个字节,即占用的内存大小是一致的,因此可以互相转化。二者是并列结构,指向sockaddr_in结构的指针也可以指向sockaddr。
-
sockaddr常用于bind、connect、recvfrom、sendto等函数的参数,指明地址信息,是一种通用的套接字地址。
-
sockaddr_in 是internet环境下套接字的地址形式。所以在网络编程中我们会对sockaddr_in结构体进行操作,使用sockaddr_in来建立所需的信息,最后使用类型转化就可以了。一般先把sockaddr_in变量赋值后,强制类型转换后传入用sockaddr做参数的函数:sockaddr_in用于socket定义和赋值;sockaddr用于函数参数。
注释中标明了属性的含义及其字节大小,这两个结构体一样大,都是16个字节,而且都有family属性,不同的是:
sockaddr用其余14个字节来表示sa_data,而sockaddr_in把14个字节拆分成sin_port, sin_addr和sin_zero分别表示端口、ip地址。sin_zero用来填充字节使sockaddr_in和sockaddr保持一样大小。
sockaddr和sockaddr_in包含的数据都是一样的,但他们在使用上有区别:
-
程序员不应操作sockaddr,sockaddr是给操作系统用的
-
程序员应使用sockaddr_in来表示地址,sockaddr_in区分了地址和端口,使用更方便。
一般的用法为:
程序员把类型、ip地址、端口填充sockaddr_in结构体,然后强制转换成sockaddr,作为参数传递给系统调用函数
4. 用法
//创建sockaddr_in结构体变量
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr)); //每个字节都用0填充
serv_addr.sin_family = AF_INET; //使用IPv4地址
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //具体的IP地址
serv_addr.sin_port = htons(1234); //端口号