1.引言-什么是socket
socket即套接字,用于描述地址和端口,是一个通信链的句柄。应用程序通过socket向网络发出请求或者回应。
sockets(套接字)编程有三种,流式套接字(SOCK_STREAM),数据报套接字(SOCK_DGRAM),原始套接字(SOCK_RAW);前两种较常用。基于TCP的socket编程是采用的流式套接字。
有可能多种协议使用同一种数据传输方式,所以在socket编程中,需要同时指明数据传输方式和协议。
2. socket常用函数
2.1 sockaddr_in
struct sockaddr_in这个结构体用来处理网络通信的地址
struct sockaddr_in {
    short            sin_family;       // 2 bytes e.g. AF_INET, AF_INET6
    unsigned short   sin_port;    //16位 2 bytes e.g. htons(3490)
    struct in_addr   sin_addr;     //32位 4 bytes see struct in_addr, below
    char             sin_zero[8];     // 8 bytes zero this if you want to
};
//另一个结构体 in_addr存放32位ip地址
struct in_addr {
    unsigned long s_addr;          // 4 bytes load with inet_pton()
};端口号需要用 htons() 函数转换
2.2 htons()、 inet_addr()和inet_ntoa()
htons()作用是将端口号由主机字节序转换为网络字节序的整数值。(host to net)
inet_addr()作用是将一个IP字符串转化为一个网络字节序的整数值,用于sockaddr_in.sin_addr.s_addr。
inet_ntoa()作用是将一个sin_addr结构体输出成IP字符串(network to ascii)。比如:
printf("%s",inet_ntoa(mysock.sin_addr));htonl()作用和htons()一样,不过它针对的是32位的(long),而htons()针对的是两个字节,16位的(short)。
与htonl()和htons()作用相反的两个函数是:ntohl()和ntohs()。
2.3 socket()
创建套接字
int socket(int af, int type, int protocol);af:IP地址的类型
-  AF_INET : IPv4 
-  AF_INET6: IPV6 
type:数据传输方式
-  SOCK_STREAM:面向连接的数据传输方式 
-  SOCK_DGRAM:无连接的数据传输方式 
protocol:传输协议
-  IPPROTO_TCP:TCP传输协议 
-  IPPTOTO_UDP:UDP传输协议 
返回值
-  成功:0 
-  失败:-1 
2.4 bind()
地址绑定,将套接字与地址关联
int bind(int sockfd, struct sockaddr *addr, socklen_t addrlen);sockfd:socket文件描述符
addr:sockaddr 结构体变量的指针
addrlen:addr 变量的大小
返回值
-  成功:0 
-  失败:-1 
2.5 connect()
建立连接,创建与指定外部端口的连接
int connect(int sockfd, struct sockaddr *serv_addr, socklen_t addrlen);sockfd:socket文件描述符
addr:sockaddr 结构体变量的指针
addrlen:addr 变量的大小
返回值
-  成功:0 
-  失败:-1 
2.6 listen()
让套接字进入被动监听状态(指当没有客户端请求时,套接字处于”睡眠“状态,只有当接收到客户端请求时,套接字才会被“唤醒”来响应请求),使得一个进程可以接收其他进程的请求,从而成为一个服务器进程
int listen(int sockfd, int backlog);sockfd:被监听的套接字的标识符
backlog:请求队列的最大长度(能存放多少个客户端请求)
-  请求队列:当套接字正在处理客户端请求时,如果有新的请求进来,套接字将把新的请求放入缓冲区,再从缓冲区取出请求 ,此缓冲区称为请求队列 
返回值
-  成功:0 
-  失败:-1 
2.7 accept()
在一个套接口接收一个连接,当套接字处于监听状态时,可以通过accept()函数来接受客户端请求,accept()会阻塞程序进行,直到有新的请求到来
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);sockfd:服务器端套接字的标识符
addr:sockaddr 结构体变量的指针
addrlen:addr 变量的大小
返回值
-  成功:返回接收到的套接字的描述符 
-  失败:-1 
2.8 send()
发送数据,将数据由指定的socket传给对方主机
int send(int s, const void * msg, int len, unsigned int falgs);s:以建立好连接的socket标识符
msg:发送的消息内容
len:发送内容的长度
falgs:一般设为0
返回值
-  成功:返回实际传送出去的字符数 
-  失败:-1 
2.9 recv()
接受数据,接收远端主机经过指定的socket传来的数据,并把数据存到buf指定的内存空间
int recv(int sock, void *buf, int len, unsigned int flags);sock:接收端套接字描述符
buf:指定缓冲区,存放接收到的数据
len:缓冲区的长度
flags:一般设为0
返回值
-  成功:返回接收到的字符数 
-  失败:-1 
3.客户端/服务端模式
在TCP/IP(Transmission Control Protocol Internet Protocol / 传输控制协议和网际协议,TCP/IP是一种网络协议,由TCP和IP两个协议组成。它负责在计算机网络中传输数据,并确保数据传输的可靠性,同时确定数据在网络中的路径。TCP/IP是网络通信的基础,也是网络上最常用的协议之一)网络应用中,通信的两个进程相互作用的主要模式是客户/服务器模式,即客户端向服务器发出请求,服务器接收请求后,提供相应的服务。因此需要一种机制为希望通信的进程间建立联系,为二者的数据交换提供同步,这就是基于客户/服务端模式的TCP/IP。
3.1 什么是TCP
TCP有三个关键步骤:三次握手,传输确认和四次挥手



构建思路:
服务端:建立socket,声明自身的端口号和地址并绑定到socket,使用listen打开监听,然后不断用accept去查看是否有连接,如果有,捕获socket,并通过recv获取消息的内容,通信完成后调用closeSocket关闭这个对应accept到的socket,如果不再需要等待任何客户端连接,那么用closeSocket关闭掉自身的socket。
客户端:建立socket,通过端口号和地址确定目标服务器,使用Connect连接到服务器,send发送消息,等待处理,通信完成后调用closeSocket关闭socket。
3.2 编程步骤
(1)服务端
1、加载套接字库,创建套接字(WSAStartup()/socket());
2、绑定套接字到一个IP地址和一个端口上(bind());
3、将套接字设置为监听模式等待连接请求(listen());
4、请求到来后,接受连接请求,返回一个新的对应于此次连接的套接字(accept());
5、用返回的套接字和客户端进行通信(send()/recv());
6、返回,等待另一个连接请求;
7、关闭套接字,关闭加载的套接字库(closesocket()/WSACleanup());
(2)客户端
1、加载套接字库,创建套接字(WSAStartup()/socket());
2、向服务器发出连接请求(connect());
3、和服务器进行通信(send()/recv());
4、关闭套接字,关闭加载的套接字库(closesocket()/WSACleanup());
3.3 学习代码
#include <stdio.h>
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")
int main(int argc, char* argv[]) 
{
        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)
        {
                printf("socket error !");
                return 0;
        }
        
        sockaddr_in sin;
        sin.sin_port = htons(8888);
        sin.sin_family = AF_INET;
        sin.sin_addr.S_un.S_addr = INADDR_ANY;
        if (bind(slisten, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR)
        {
                printf("bind error !");
        }
        
        if (listen(slisten, 5) == SOCKET_ERROR)
        {
                printf("listen error !");
                return 0;
        }
        
        SOCKET sClient;
        sockaddr_in remoteAddr;
        int nAddrlen = sizeof(remoteAddr);
        char revData[255];
        while (true)
        {
                printf("Wating for connecting... \r\n");
                sClient = accept(slisten, (SOCKADDR *)&remoteAddr, &nAddrlen);
                if (sClient == INVALID_SOCKET)
                {
                        printf("accept error !");
                        continue;
                }
                printf("accept a connection: %s \r\n", inet_ntoa(remoteAddr.sin_addr));
                
                int ret = recv(sClient, revData, 255, 0);
                if (ret > 0)
                {
                        revData[ret] = 0x00;
                        printf(revData);
                }
                
                const char * sendData = "hello, TCP Client";
                send(sClient, sendData, strlen(sendData), 0);
                closesocket(sClient);
                 
        }
        
        closesocket(slisten);
        WSACleanup();
        return 0;
}client.cpp
#include <WINSOCK2.H>
#include <STDIO.H>
#include <iostream>
#include <cstring>
using namespace std;
#pragma comment(lib, "ws2_32.lib")
int main()
{
    WORD sockVersion = MAKEWORD(2,2);
    WSADATA data;
    if (WSAStartup(sockVersion, &data) != 0)
    {
        return 0;
    }
    while (true)
    {
        SOCKET sclient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
        if (sclient == INVALID_SOCKET)
        {
            printf("Invalid socket!");
            return 0;
        }
        sockaddr_in serAddr;
        serAddr.sin_family = AF_INET;
        serAddr.sin_port = htons(8888);
        serAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
        if (connect(sclient, (sockaddr *)&serAddr, sizeof(serAddr)) == SOCKET_ERROR)
        {
            printf("connect error!");
            closesocket(sclient);
            return 0;
        }
        string data;
        cin>>data;
        const char * sendData;
        sendData = data.c_str();
        send(sclient, sendData, strlen(sendData), 0);
        char recData[255];
        int ret = recv(sclient, recData, 255, 0);
        if (ret > 0)
        {
            recData[ret] = 0x00;
            printf(recData);
        }
        closesocket(sclient);
    }
    WSACleanup();
    return 0;
}3.4 客户端一直发送数据代码
server.cpp
#include <stdio.h>
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")
int main(int argc, char* argv[])
{
    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)
    {
        printf("socket error!");
        return 0;
    }
    sockaddr_in sin;
    sin.sin_family = AF_INET;
    sin.sin_port = htons(6000);
    sin.sin_addr.S_un.S_addr = INADDR_ANY;
    if(bind(slisten, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR)
    {
        printf("bind error!");
        return 0;
    }
    if (listen(slisten, 5) == SOCKET_ERROR)
    {
        printf("listen error!");
        return 0;
    }
    SOCKET sClient;
    sockaddr_in remoteAddr;
    int nAddrlen = sizeof(remoteAddr);
    char revData[255];
    while (true)
    {
        printf("Waiting for connecting... \n");
        sClient = accept(slisten, (SOCKADDR *)&remoteAddr, &nAddrlen);
        if (sClient == SOCKET_ERROR)
        {
            printf("accept error !");
            continue;
        }
        int ret = recv(sClient, revData, 255, 0);
        if (ret > 0 )
        {
            revData[ret] = 0x00;
            printf("Received: %s\n", revData);
        }
        const char * sendData = "Hello World from Server";
        send(sClient, sendData, strlen(sendData), 0);
        printf("Sent: %s\n", sendData);
        // Keep receiving messages
        while (true)
        {
            ret = recv(sClient, revData, 255, 0);
            if (ret > 0)
            {
                revData[ret] = 0x00;
                printf("Received: %s\n", revData);
            }
        }
        closesocket(sClient);
    }
    closesocket(slisten);
    WSACleanup();
    return 0;
}client.cpp
#include <winsock2.h>
#include <stdio.h>
#include <iostream>
#include <cstring>
#pragma comment(lib, "ws2_32.lib")
int main()
{
    WORD sockVersion = MAKEWORD(2,2);
    WSADATA data;
    if(WSAStartup(sockVersion, &data) != 0)
    {
        return 0;
    }
    SOCKET sclient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if(sclient == INVALID_SOCKET)
    {
        printf("Invalid socket!");
        return 0;
    }
    sockaddr_in serAddr;
    serAddr.sin_family = AF_INET;
    serAddr.sin_port = htons(6000);
    serAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
    if (connect(sclient, (sockaddr *)&serAddr, sizeof(serAddr)) == SOCKET_ERROR)
    {
        printf("connect error!");
        closesocket(sclient);
        return 0;
    }
    const char * sendData = "Hello World from Client";
    send(sclient, sendData, strlen(sendData), 0);
    printf("Sent: %s\n", sendData);
    char recData[255];
    int ret = recv(sclient, recData, 255, 0);
    if (ret > 0)
    {
        recData[ret] = 0x00;
        printf("Received: %s\n", recData);
    }
    // Keep sending messages
    while (true)
    {
        const char * helloData = "Hello";
        send(sclient, helloData, strlen(helloData), 0);
        printf("Sent: %s\n", helloData);
        // Sleep for 1 second
        Sleep(1000);
    }
    closesocket(sclient);
    WSACleanup();
    return 0;
}3.5 用户在客户端输入数据代码
server.cpp
#include <stdio.h>
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")
int main(int argc, char* argv[])
{
    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)
    {
        printf("socket error!");
        return 0;
    }
    sockaddr_in sin;
    sin.sin_family = AF_INET;
    sin.sin_port = htons(6000);
    sin.sin_addr.S_un.S_addr = INADDR_ANY;
    if(bind(slisten, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR)
    {
        printf("bind error!");
        return 0;
    }
    if (listen(slisten, 5) == SOCKET_ERROR)
    {
        printf("listen error!");
        return 0;
    }
    SOCKET sClient;
    sockaddr_in remoteAddr;
    int nAddrlen = sizeof(remoteAddr);
    char revData[255];
    while (true)
    {
        printf("Waiting for connecting... \n");
        sClient = accept(slisten, (SOCKADDR *)&remoteAddr, &nAddrlen);
        if (sClient == SOCKET_ERROR)
        {
            printf("accept error !");
            continue;
        }
        // Keep receiving messages
        while (true)
        {
            int ret = recv(sClient, revData, 255, 0);
            if (ret > 0)
            {
                revData[ret] = 0x00;
                printf("Received: %s\n", revData);
            }
            else if (ret == 0) // Connection closed
            {
                printf("Client disconnected.\n");
                closesocket(sClient);
                break;
            }
            else if (ret == SOCKET_ERROR) // Error receiving
            {
                printf("recv error!\n");
                closesocket(sClient);
                break;
            }
        }
    }
    closesocket(slisten);
    WSACleanup();
    return 0;
}client.cpp
#include <winsock2.h>
#include <stdio.h>
#include <iostream>
#include <cstring>
#pragma comment(lib, "ws2_32.lib")
int main()
{
    WORD sockVersion = MAKEWORD(2,2);
    WSADATA data;
    if(WSAStartup(sockVersion, &data) != 0)
    {
        return 0;
    }
    SOCKET sclient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if(sclient == INVALID_SOCKET)
    {
        printf("Invalid socket!");
        return 0;
    }
    sockaddr_in serAddr;
    serAddr.sin_family = AF_INET;
    serAddr.sin_port = htons(6000);
    serAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
    if (connect(sclient, (sockaddr *)&serAddr, sizeof(serAddr)) == SOCKET_ERROR)
    {
        printf("connect error!");
        closesocket(sclient);
        return 0;
    }
    char sendData[255];
    while (true)
    {
        std::cout << "Enter message: ";
        std::cin.getline(sendData, 255);
        if (strlen(sendData) == 0) {
            continue;
        }
        send(sclient, sendData, strlen(sendData), 0);
        printf("Sent: %s\n", sendData);
    }
    closesocket(sclient);
    WSACleanup();
    return 0;
}4.在CLion建立客户端和服务端
修改CLion配置如下图


切换到此,点击运行即可进行通信

5. 运行效果



















