c++ & c 实现 TCP/UDP通讯




向前链接 Python TCP/UDP 编程: http://be-sunshine.cn/index.php/2017/07/26/python3-udptcp-ip/

三个任务:

实现tcp通信

实现udp通信

使用tcp实现客户加入通知全部客户,使用轮询方法

基础知识

套接字socket(int af,int type,int protocol)函数共三个参数.第一个参数af用来指定地址族,在Windows下可以使用的参数值有多个,但真正可以使用的只有两个,分别是 AF_INET 和 PF_INET。 这两个宏在winsock2.h下是相同的.

为了保证兼容性,尽量使用PF_INET比较好.

第二个参数type是指定新套接字描述符的类型.这里可以用的值通常有3个.分别是SOCK_STREAM,SOCK_DGRAM和SOCK_RAW,分别表示流套接字,数据包套接字和原始协议接口.

第三个参数是协议.

如果第二个参数使用SOCK_STREAM,name第三个参数应该使用IPPROTO_TCP.SOCK_DGRAM:IPPROTO_UDP.
即若第二个参数是这两个,第三个参数可以是0.如果是RAW,则第三个参数必须指定.

调用成功会返回一个新的套接字描述符.如果失败,则返回 INVALID_SOCKET.如果调用失败,想要知道原因,需要调用WSAGetLastError()得到错误码.

面向连接协议的函数

这里来介绍一些基本的函数:bind(),listen(),accept(),connect(),send()和recv().

socket()只是新建了一个socket描述符,但内部信息尚未完善,,比如在网络通信时本地的端口和IP等.
这些信息需要使用bind()方法来完成.

bind()函数定义如下:

int bind(SOCKET s,const struct sockaddr FAR *name,int namelen)

第一个参数是套接字描述符,也就是我们需要用到的socket
第二个参数是一个sockaddr结构体,提供套接字一个地址和端口信息.
第三个参数是namelen是sockaddr的大小.

其中第二个参数结构体定义如下:

struct sockaddr{
    u_short sa_family;/*address family*/
    char sa_data[14];/*up to 14 bytes of direct address.*/
};

但是需要使用转换将sockaddr之前的协议结构体 sockaddr_in转换成sockaddr.

struct sockaddr_in {
    short sin_family;
    u_short sin_port;
    struct in_addr sin_addr;
    char sin_zero[8];
}

struct in_addr{
    union{
        struct{u_char s_b1,s_b2,s_b3,s_b4;}  S_un_b;
        struct{u_short s_w1,s_w2;}  S_un_w;
        u_long  S_addr;
    }S_un;
};

其中点分十进制ip转换成无符号长整型的函数是


unsigned long inet_addr(const char FAR *cp);

逆函数:转换成点分十进制


char FAR * inet_ntoa(struct in_addr in);

sin_port需要的转换函数:


htons()和htonl()
逆向:ntohs()和ntohl()

监听端口的函数定义如下:

int listen(SOCKET s,int backlog);

第一个参数是描述符,第二个参数是允许进入连接请求队列的个数.backlog的最大值由系统指定.
在winsock2.h中,其最大值由SOMAXCONN表示,该值的定义如下:

#define SOMAXCONN 0x7fffffff

接受连接请求的函数定义如下:

SOCKET accept(SOCKET s,struct sockaddr FAR *addr,int FAR *addrlen);
//FAR:近指针

任务一:TCP

服务端

WSAStartup()->socket()->bind()->listen()->accept()->send()/recv()->closesocket()->WSACleanup()

客户端

WSAStartuo()->socket()->connect()->send()/recv()->closesocket()->WSACleanup()

Code:

服务端:

#include<stdio.h>
#include<winsock2.h>
#pragma comment (lib,"ws2_32")

int main(){
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2,2),&wsaData);

    //创建套接字
    SOCKET sLisent = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);

    //对sockaddr_in结构体填充地址,端口等信息
    struct sockaddr_in ServerAddr;
    ServerAddr.sin_family=AF_INET;
    ServerAddr.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");
    ServerAddr.sin_port=htons(1234);

    //绑定套接字与地址信息
    bind(sLisent,(SOCKADDR *)&ServerAddr,sizeof(ServerAddr));

    //监听端口
    listen(sLisent,SOMAXCONN);

    //获取请求连接
    struct sockaddr_in ClientAddr;
    int nSize=sizeof(ClientAddr);

    SOCKET sClient=accept(sLisent,(SOCKADDR *)&ClientAddr,&nSize);
    //输出客户端使用的IP地址和端口号
    printf("Client IP=%s:%d\r\n",inet_ntoa(ClientAddr.sin_addr),ntohs(ClientAddr.sin_port));

    //发送消息
    char szMsg[MAXBYTE]={0};
    lstrcpy(szMsg,"hello Client!\r\n");
    send(sClient,szMsg,strlen(szMsg)+sizeof(char),0);

    //接收消息
    recv(sClient,szMsg,MAXBYTE,0);
    printf("Client Msg : %s \r\n",szMsg);

    WSACleanup();

    getchar();

    return 0;
}


客户端:

#include<stdio.h>
#include<winsock2.h>
#pragma comment (lib,"ws2_32")

int main(){
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2,2),&wsaData);

    //创建套接字
    SOCKET sServer = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);

    //对sockaddr_in结构体填充地址,端口等信息
    struct sockaddr_in ServerAddr;
    ServerAddr.sin_family=AF_INET;
    ServerAddr.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");
    ServerAddr.sin_port=htons(1234);

    //连接服务器
    connect(sServer,(SOCKADDR *)&ServerAddr,sizeof(ServerAddr));

    char szMsg[MAXBYTE]={0};

    //接收消息
    recv(sServer,szMsg,MAXBYTE,0);
    printf("Server Msg: %s \r\n",szMsg);

    //发送消息
    lstrcpy(szMsg,"hello Server!\r\n");
    send(sServer,szMsg,strlen(szMsg)+sizeof(char),0);

    WSACleanup();

    getchar();

    return 0;
}

任务二:UDP

服务端

socket()->bind()->sendto()/recvfrom()->closesocket()

客户端

socket()->sendto()/recvfrom()->closesocket()

code:

服务端:

#include<stdio.h>
#include<winsock2.h>
#pragma comment (lib,"ws2_32")

int main(){
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2,2),&wsaData);

    //创建套接字
    SOCKET sServer = socket(PF_INET,SOCK_DGRAM,IPPROTO_UDP);

    //对socketaddr_in填充
    struct sockaddr_in ServerAddr;
    ServerAddr.sin_family=AF_INET;
    ServerAddr.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");
    ServerAddr.sin_port=htons(1234);

    //绑定套接字与地址信息
    bind(sServer,(SOCKADDR *)&ServerAddr,sizeof(ServerAddr));

    //接收消息
    char szMsg[MAXBYTE]={0};
    struct sockaddr_in ClientAddr;
    int nSize=sizeof(ClientAddr);
    recvfrom(sServer,szMsg,MAXBYTE,0,(SOCKADDR*)&ClientAddr,&nSize);
    printf("Client Msg: %s \r\n",szMsg);
    printf("Client IP=%s: %d\r\n",inet_ntoa(ClientAddr.sin_addr),ntohs(ClientAddr.sin_port));

    //发送消息
    lstrcpy(szMsg,"hello Client!\r\n");
    nSize=sizeof(ClientAddr);
    sendto(sServer,szMsg,strlen(szMsg)+sizeof(char),0,(SOCKADDR*)&ClientAddr,nSize);

    WSACleanup();
    getchar();


    return 0;
}

客户端:

#include<stdio.h>
#include<winsock2.h>
#pragma comment (lib,"ws2_32")

int main(){
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2,2),&wsaData);

    //创建套接字
    SOCKET sClient = socket(PF_INET,SOCK_DGRAM,IPPROTO_UDP);

    //对socketaddr_in填充
    struct sockaddr_in ServerAddr;
    ServerAddr.sin_family=AF_INET;
    ServerAddr.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");
    ServerAddr.sin_port=htons(1234);

    //发送消息
    char szMsg[MAXBYTE]={0};
    lstrcpy(szMsg,"Hello Server!\r\n");
    int nSize=sizeof(ServerAddr);
    sendto(sClient,szMsg,strlen(szMsg)+sizeof(char),0,(SOCKADDR *)&ServerAddr,nSize);

    //接收消息
    nSize=sizeof(ServerAddr);
    recvfrom(sClient,szMsg,MAXBYTE,0,(SOCKADDR *)&ServerAddr,&nSize);
    printf("Server Msg : %s\r\n",szMsg);

    WSACleanup();

    getchar();
    return 0;
}

任务三:轮询式多客户端连接

code:

服务端:

#include<stdio.h>
#include<winsock2.h>
#include <windows.h>
#include<vector>
#pragma comment (lib,"ws2_32")
using namespace std;

int main(){
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2,2),&wsaData);

    //创建套接字
    SOCKET sLisent = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);

    //对sockaddr_in结构体填充地址,端口等信息
    struct sockaddr_in ServerAddr;
    ServerAddr.sin_family=AF_INET;
    ServerAddr.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");
    ServerAddr.sin_port=htons(1234);

    //绑定套接字与地址信息
    bind(sLisent,(SOCKADDR *)&ServerAddr,sizeof(ServerAddr));

    vector<SOCKET> cStack;

    while(1){
        //监听端口
        listen(sLisent,SOMAXCONN);

        //获取请求连接
        struct sockaddr_in ClientAddr;
        int nSize=sizeof(ClientAddr);

        SOCKET sClient=accept(sLisent,(SOCKADDR *)&ClientAddr,&nSize);
        cStack.push_back(sClient);
        //输出客户端使用的IP地址和端口号
        printf("Client IP=%s:%d\r\n",inet_ntoa(ClientAddr.sin_addr),ntohs(ClientAddr.sin_port));

        //向全部客户端发送消息
        for(int i=0;i<cStack.size();++i){
            char szMsg[MAXBYTE]={0};
            lstrcpy(szMsg,"hello Client!\r\n");
            send(cStack[i],szMsg,strlen(szMsg)+sizeof(char),0);

            //接收消息
            recv(cStack[i],szMsg,MAXBYTE,0);
            printf("Client Msg : %s \r\n",szMsg);
        }
    }
    WSACleanup();

    getchar();

    return 0;
}

客户端:

#include<stdio.h>
#include<winsock2.h>
#include<windows.h>
#pragma comment (lib,"ws2_32")

int main(){
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2,2),&wsaData);

    //创建套接字
    SOCKET sServer = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);

    //对sockaddr_in结构体填充地址,端口等信息
    struct sockaddr_in ServerAddr;
    ServerAddr.sin_family=AF_INET;
    ServerAddr.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");
    ServerAddr.sin_port=htons(1234);
    while(1){
        //连接服务器
        connect(sServer,(SOCKADDR *)&ServerAddr,sizeof(ServerAddr));

        char szMsg[MAXBYTE]={0};

        //接收消息
        printf("接收消息\n");
        recv(sServer,szMsg,MAXBYTE,0);
        printf("Server Msg: %s \r\n",szMsg);

        //发送消息
        lstrcpy(szMsg,"hello Server!\r\n");
        send(sServer,szMsg,strlen(szMsg)+sizeof(char),0);

    }
    WSACleanup();

    getchar();

    return 0;
}

以上代码编译连接以后直接运行.exe即可,注意,需要在IDE中添加依赖库libws2_32.a