初探 Socket

本文最后更新于:2 年前


起因是寒假没事干搞了个70块一年的云服务器玩玩,试图整一个联机游戏demo

感谢大佬:https://www.cnblogs.com/dolphinX/p/3460545.html

socket是什么

https://www.bilibili.com/video/BV1eg411G7pW?from=search&seid=14838178887423899974&spm_id_from=333.337.0.0

这里以TCP为例

socket通信基本流程

通信基本流程如上图,在accept之后,客户端和服务端双方都获得了连接对方的Socket,随后即可不断地进行点对点地Send和Receive

通信demo

注:以下程序编译指令需加:-lwsock32

以Windows下,C++为例

顺序即实际流程顺序:

int socket(int domain, int type, int protocol);
//客户端和服务端均创建Socket,返回它的标识符,后续用这个int来代表这一socket
//domain: 协议族,常用AF_INET表示使用IPv4
//type:socket类型,常用SOCK_STREAM
//protocol:所用协议,常用IPPROTO_TCP
int bind(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen);
//服务端,绑定一个端口,返回错误信号
//用法见下,其中slisten服务端的socket标识符:
sockaddr_in sin; //这是一个地址对象
sin.sin_family = AF_INET;
sin.sin_port = htons(8888);//绑定的端口,要改的只有这里
sin.sin_addr.S_un.S_addr = INADDR_ANY;
if(bind(slisten, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR) {
    cout << "bind error !\n";
}
int listen(SOCKET sockfd, int backlog);
//服务端,开始监听,返回错误信号(这是开启监听的命令,不是阻塞方法)
//backlog:最大可排队连接数
int connect(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen);
//客户端:连接服务端,返回错误信号
//用法如下,其中sclient为客户端的socket标识符:
sockaddr_in serAddr;
serAddr.sin_family = AF_INET;
serAddr.sin_port = htons(8888);//端口
serAddr.sin_addr.S_un.S_addr = inet_addr("124.223.118.118");//IP
if(connect(sclient, (sockaddr *)&serAddr, sizeof(serAddr)) == SOCKET_ERROR) {
    cerr << "connect error !\n";
}
int accept(SOCKET sockfd, struct sockaddr *addr, socklen_t *addrlen);
//服务端,阻塞方法,等待来自客户端的connect,等到后返回客户端的socket标识符
//到这一步,服务端知晓了客户端的SOCKET标识和IP,可以记录下来便于未来主动向客户端发送消息
int send(SOCKET sclient, const char *msg, int len, int flags);
int recv(SOCKET sclient, char *buf, int len, int flags);
//收发消息服务端和客户端均可使用,sclient处都使用accept方法得到的客户端的SOCKET标识即可
//这里的通信就自由了,客户度send,服务端recv,随后还可以服务端send,客户端recv进行一个回复;

另一提:网络间通信,由于网络波动等,一次消息的发送或接收失败很正常,应做出错误处理而非中断程序;

客户端:

#include<WINSOCK2.H>
#include<iostream>
#include<cstring>
#include<ctime>
using namespace std;
#pragma comment(lib, "ws2_32.lib")

const char *ipv4 = "124.223.118.118";
const unsigned short hton = 8888;

bool sendToServer(const char *msg){
    //连接
    SOCKET sclient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if(sclient == INVALID_SOCKET) {
        cout << "invalid socket!\n";
        return false;
    }

    sockaddr_in serAddr;
    serAddr.sin_family = AF_INET;
    serAddr.sin_port = htons(hton);//端口
    serAddr.sin_addr.S_un.S_addr = inet_addr(ipv4);//IP
    if(connect(sclient, (sockaddr *)&serAddr, sizeof(serAddr)) == SOCKET_ERROR) {
        //连接失败
        cerr << "connect error !\n";
        closesocket(sclient);
        return false;
    }

    //send()用来将数据由指定的socket传给对方主机
    if(send(sclient, msg, strlen(msg), 0) == -1){
        cerr << "send failed!\n";
        closesocket(sclient);
        return false;
    }

    //recv()来自服务端的回信
    char recData[255];
    int ret = recv(sclient, recData, 255, 0);
    if(ret>0) {
        recData[ret] = 0x00;
        cout << recData;
    }
    closesocket(sclient);
    return true;
}

int main(){
    //初始化
    WORD sockVersion = MAKEWORD(2, 2);
    WSADATA data;
    if(WSAStartup(sockVersion, &data)!=0) {
        return 0;
    }

    while(true) {
        string data;
        cin >> data;
        sendToServer(data.c_str());
        if(data == "END") break;
    }

    WSACleanup();
    return 0;
}

服务端:

#include <iostream>
#include <stdio.h>
#include <winsock2.h>
using namespace std;

#pragma comment(lib,"ws2_32.lib")

int main(int argc, char* argv[]) {
    //初始化WSA
    WORD sockVersion = MAKEWORD(2,2);
    WSADATA wsaData;
    if(WSAStartup(sockVersion, &wsaData)!=0) {
        return 0;
    }

    //创建套接字
    SOCKET slisten = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if(slisten == INVALID_SOCKET) {
        cout << "socket error !\n";
        return 0;
    }

    //绑定IP和端口
    sockaddr_in sin;
    sin.sin_family = AF_INET;
    sin.sin_port = htons(8888);
    sin.sin_addr.S_un.S_addr = INADDR_ANY;
    if(bind(slisten, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR) {
        cout << "bind error !\n";
    }

    //开始监听
    if(listen(slisten, 5) == SOCKET_ERROR) {
        cout << "listen error !\n";
        return 0;
    }

    //循环接收数据
    SOCKET sClient;
    sockaddr_in remoteAddr;
    int nAddrlen = sizeof(remoteAddr);
    char revData[255];
    while (true) {
        cout << "Waiting...\n";
        sClient = accept(slisten, (SOCKADDR *)&remoteAddr, &nAddrlen);
        if(sClient == INVALID_SOCKET)
        {
            cout << "accept error !\n";
            continue;
        }
        cout << "received connect: " <<  inet_ntoa(remoteAddr.sin_addr) << " \r\n";

        //接收数据
        int ret = recv(sClient, revData, 255, 0);
        if(ret > 0) {
            revData[ret] = 0x00;
            cout << revData << '\n';
        }

        //发送数据
        const char * sendData = "how are you? \n";
        send(sClient, sendData, strlen(sendData), 0);
        closesocket(sClient);
    }

    closesocket(slisten);
    WSACleanup();
    return 0;
}

避个坑:服务端的回信不是必须的(三次握手在更加底层的地方,已经有人完成好了),此处只是为了方便查看通信结果;


初探 Socket
http://www.lxtyin.ac.cn/2022/01/17/2022-01-17-Socket入门/
作者
lx_tyin
发布于
2022年1月17日
许可协议