【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);  //端口号
posted @ 2024-08-12 16:57  AirCL  阅读(95)  评论(0编辑  收藏  举报