【计算机网络】基于TCP的服务器端和客户端

 🔥博客主页: 我要成为C++领域大神

🎥系列专栏【C++核心编程】 【计算机网络】 【Linux编程】 【操作系统】

❤️感谢大家点赞👍收藏⭐评论✍️

本博客致力于分享知识,欢迎大家共同学习和交流。

TCP客户端搭建过程
1、加载库
2、创建socket套接字
3、连接
4、收发数据
5、关闭套接字
6、卸载库

TCP服务器搭建流程
服务器的特点:端口和IP地址必须绑定
1、加载库
2、创建套接字
3、绑定IP
4、监听
5、接受连接
6、接收客户端发送的数据
7、返回客户端一些信息
8、关闭套接字
9、卸载库

服务端

TCP通信的服务端在UDP通信服务端的基础上,增加了监听和接受连接的步骤。另外接收数据和发送数据使用的是recv函数和send函数。

加载库

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

#pragma comment(lib,"Ws2_32.lib")表示链接Ws2_32.lib这个库。

//1、加载库
WORD version = MAKEWORD(2, 2);
WSADATA data = {};
int err=WSAStartup(version,&data);
if (0 != err) {
    cout << "WSAStartup fail" << endl;
    return 1;
}
else {
    cout << "WSAStartup success" << endl;
}
if (2 != HIBYTE(data.wVersion) || 2 != LOBYTE(data.wVersion)) {
    //虽然加载库成功了,但是版本加载不对
    cout << "version error" << endl;
    WSACleanup();
    return 1;
}
else {
    cout << "WSAStartup version success" << endl;
}

创建套接字

创建套接字需要使用socket函数。函数的3个参数,分别代表 地址族,套接字类型,通讯协议

根据返回值是否为异常,来判断套接字创建是否成功。

若创建失败,需要卸载库,并终止运行。

SOCKET WSAAPI socket(
//VS官方帮助文档中的[in]表示输入的参数
  [in] int af,
  [in] int type,
  [in] int protocol
);
 SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
 if (INVALID_SOCKET == sock) {
     cout << "socket fail" << endl;
     WSACleanup();
     return 1;
 }
 else
 {
     cout << "socket success" << endl;
 }

绑定IP和端口号

绑定IP地址使用的函数是bind函数。函数的返回值是一个整型,根据函数的返回值来判断IP绑定是否成功,失败返回SOCKET_ERROR,此时不仅要卸载库,还要关闭套接字。

bind函数有三个参数,套接字,sockaddr结构体类型的指针,结构体大小

int WSAAPI bind(
  [in] SOCKET         s,
  [in] const sockaddr *name,
  [in] int            namelen
);

通过帮助文档查看sockaddr结构体,给出了sockaddr和sockaddr_in。为了方便设置端口号和IP,我们使用sockaddr_in,传入函数时,再强制类型转换将其地址转换为sockaddr*类型。

typedef struct sockaddr {
  u_short sa_family;
  char    sa_data[14];
} SOCKADDR, *PSOCKADDR, *LPSOCKADDR;
typedef struct sockaddr_in {
  short          sin_family;
  u_short        sin_port;
  struct in_addr sin_addr;
  char           sin_zero[8];
} SOCKADDR_IN, *PSOCKADDR_IN, *LPSOCKADDR_IN;

sockaddr_in结构体的成员有一个in_addr结构体,在帮助文档中搜索查看,发现结构体里面是个联合体。用于传入IP。

struct in_addr {
  union {
    struct {
      u_char s_b1;
      u_char s_b2;
      u_char s_b3;
      u_char s_b4;
    } S_un_b;
    struct {
      u_short s_w1;
      u_short s_w2;
    } S_un_w;
    u_long S_addr;
  } S_un;
};
//3、绑定IP地址
sockaddr_in addServer = {};
addServer.sin_family = AF_INET;
addServer.sin_port = htons(98765);
addServer.sin_addr.S_un.S_addr= INADDR_ANY;
err = bind(sock, (sockaddr*)&addServer, sizeof(addServer));
if (err == SOCKET_ERROR) {
    cout << "bind fail:" << WSAGetLastError() << endl;
    closesocket(sock);
    WSACleanup();
    return 1;
}
else
{
    cout << "bind success" << endl;
}

这里需要注意的是,我们在设置端口号时,需要转一下网络字节序。如果不转换的话,PC之间的客户端和服务端进行数据传输,由于都是小端存储,所以不会有影响。但是如果对端不是PC,可能就会有问题。

sockServer.sin_port = htons(9876);  //设置端口号,并转换网络字节序

监听

监听使用listen函数,监听socket,若正常监听返回0,否则返回SOCKET_ERROR。函数有两个参数,第一个参数是套接字,即监听所用套接字;第二个参数是处理连接队列的最大长度,监听是有监听队列的,即最多可以同时监听多少个连接。

err = listen(sock, 100);
if (err == SOCKET_ERROR) {
    cout << "listen fail:" << WSAGetLastError() << endl;
    closesocket(sock);
    WSACleanup();
    return 1;
}
else
{
    cout << "listen success" << endl;
}

接受连接

TCP是面向连接的协议,所以需要去等待连接。
连接使用accept函数,默认也是阻塞状态。
函数的返回值是一个SOCKET类型的值,即实际连接的套接字的句柄。连接成功则返回连接产生的套接字,失败返回INVALID_SOCKET。第一个参数是输入参数,处于监听状态的套接字;第二个参数是输出参数,输出我们连接端的地址;第三个参数是输入输出参数,即第二个参数的长度。

SOCKET WSAAPI accept(
  [in]      SOCKET   s,
  [out]     sockaddr *addr,
  [in, out] int      *addrlen
);
  SOCKET sockTalk=accept(sock, (sockaddr*)&addr, &size);
  if (INVALID_SOCKET != sock) {
      cout << "accept fail" <<WSAGetLastError()<< endl;
      return 1;
  }
  else
  {
      cout << "client ip:"<<inet_ntoa(addr.sin_addr)<<endl;
  }

接收消息

与UDP不同的是,TCP接收消息使用的是recv函数。函数有4个参数,第一个参数是连接产生的套接字;第二个参数是输出参数,字符串类型,用来存放收到的消息;第三个参数是字符串长度;第四个参数是标志位(填0即可);

若能正常接收,则返回值>0;如果连接关闭,则返回0,如果接收异常返回错误信息。

recv参数中的socket就是连接成功产生的套接字,只对应一个IP,所以函数中不需要客户端IP参数了。

int WSAAPI recv(
  [in]                SOCKET   s,
  [out]               char     *buf,
  [in]                int      len,
  [in]                int      flags,
);
 nRecvNum=recv(sockTalk, recvBuf, sizeof(recvBuf),1);
 if (nRecvNum > 0) {
     cout << "client say:" << recvBuf << endl;
 }
 else
 {
     cout << "recv error" << WSAGetLastError() << endl;
 }

发送消息

同样的,发送消息使用的是send函数,函数有4个参数。第一个参数是连接产生的套接字,第二个参数是字符串,用来存放我们发送的内容,第三个参数是字符串的长度,第四个参数是标志位,填0即可。

int WSAAPI send(
  [in] SOCKET         s,
  [in] const char     *buf,
  [in] int            len,
  [in] int            flags,
);
 nSendNum = send(sockTalk, sendBuf, strlen(sendBuf), 1);
 if (nSendNum > 0) {
     cout << "server say:" << sendBuf << endl;
 }
 else
 {
     cout << "send error" << WSAGetLastError() << endl;
 }
  }

关闭套接字

关闭我们上面创建的套接字,使用的是closesocket函数,传入的参数是套接字

//6、关闭套接字
closesocket(sock);

卸载库

使用WSACleanup函数即可,没有参数。

//7、卸载库
WSACleanup();

服务端代码

#include<iostream>
#include<Winsock2.h>
#pragma comment(lib,"Ws2_32.lib")
using namespace std;
int main() {
    //1、加载库
    WORD version=MAKEWORD(2,2);
    WSADATA Data = {};
    int err=WSAStartup(version, &Data);
    if (err != 0) {
        cout << "WSAStartup fail" << endl;
        return 1;
    }
    if (2 != HIBYTE(Data.wVersion) || 2 != LOBYTE(Data.wVersion)) {
        cout << "version err" << endl;
        WSACleanup();
        return 1;
    }
    else { cout << "WSAStartup success" << endl; }
    //2、创建套接字
    SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (INVALID_SOCKET == sock) {
        cout << "socket fail" << endl;
        WSACleanup();
        return 1;
    }
    else
    {
        cout << "socket success" << endl;
    }
    //3、绑定IP地址
    sockaddr_in addServer = {};
    addServer.sin_family = AF_INET;
    addServer.sin_port = htons(98765);
    addServer.sin_addr.S_un.S_addr= INADDR_ANY;
    err = bind(sock, (sockaddr*)&addServer, sizeof(addServer));
    if (err == SOCKET_ERROR) {
        cout << "bind fail:" << WSAGetLastError() << endl;
        closesocket(sock);
        WSACleanup();
        return 1;
    }
    else
    {
        cout << "bind success" << endl;
    }
    //4、监听
    err = listen(sock, 100);
    if (err == SOCKET_ERROR) {
        cout << "listen fail:" << WSAGetLastError() << endl;
        closesocket(sock);
        WSACleanup();
        return 1;
    }
    else
    {
        cout << "listen success" << endl;
    }
    sockaddr_in addr;
    int size = sizeof(addr);
    char recvBuf[4096] = "";
    char sendBuf[4096] = "";
    int nRecvNum = 0;
    int nSendNum = 0;
    while (true)
    {//5、接受连接(产生的套接字专门用来跟连接的客户端进行通信)
        SOCKET sockTalk=accept(sock, (sockaddr*)&addr, &size);
        if (INVALID_SOCKET == sock) {
            cout << "accept fail: " <<WSAGetLastError()<< endl;
            return 1;
        }
        else
        {
            cout << "client ip:"<<inet_ntoa(addr.sin_addr)<<endl;
        }
     while (true) {
     //6、接收数据
       nRecvNum=recv(sockTalk, recvBuf, sizeof(recvBuf),0);
       if (nRecvNum > 0) {
           cout << "client say:" << recvBuf << endl;
       }
       else
       {
           cout << "recv error" << WSAGetLastError() << endl;
           break;
       }

    //7、发送数据
       gets_s(sendBuf);
       nSendNum = send(sockTalk, sendBuf, strlen(sendBuf), 0);
       if (nSendNum ==SOCKET_ERROR) {
           cout << "send error" << WSAGetLastError() << endl;
           break;
       }
        }
    
     closesocket(sockTalk);//关闭专门用来通信的套接字
    }

    //8、关闭套接字,卸载库
    closesocket(sock);
    WSACleanup();
    return 0;
}

客户端

与服务端相比,客户端没有绑定IP,监听和接受连接过程。但是需要与服务端进行连接。

连接

连接使用的函数是connect函数,connect函数的返回值是一个整型,若返回SOCKET_ERROR则说明连接失败,否则成功。函数有三个参数。第一个参数是创建的套接字;第二个参数是服务端的IP,第三个参数是第二个参数的大小。

int WSAAPI connect(
  [in] SOCKET         s,
  [in] const sockaddr *name,
  [in] int            namelen
);
 //连接服务端
 sockaddr_in Server_addr;
 Server_addr.sin_family = AF_INET;
 Server_addr.sin_port = htons(98765);
 Server_addr.sin_addr.S_un.S_addr = inet_addr("10.51.225.72");
 err=connect(sock,(sockaddr*) & Server_addr, sizeof(Server_addr));
 if (err == SOCKET_ERROR) {
     cout << "connect fail" << endl;
     cout << "ERROR:" << WSAGetLastError() << endl;
     closesocket(sock);
     WSACleanup();
     return 1;
 }
 else
 {
     cout << "connect success" << endl;
 }

运行结果

  • 20
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,我可以为您提供一些关于创建基于TCP服务器端/客户端的指导。 首先,您需要了解一些基本概念和步骤: 1. TCP/IP协议:TCP/IP协议是一种常用的网络通信协议,用于控制网络上的数据传输。它是由两个协议组成的:TCP(传输控制协议)和IP(网际协议)。 2. 服务器端服务器端是指在网络上提供服务的计算机程序。服务器端通常需要创建一个socket,监听客户端的连接请求,并接受来自客户端的请求,处理请求并返回响应。 3. 客户端客户端是指连接到服务器端的计算机程序。客户端通常需要创建一个socket,连接到服务器端,并发送请求,等待服务器端响应。 接下来,我们可以按照以下步骤创建一个基于TCP服务器端/客户端,用于收发文件: 1. 创建服务器端socket:使用socket()函数创建一个新的socket,指定协议族(AF_INET表示IPv4)和socket类型(SOCK_STREAM表示流式套接字)。 2. 绑定服务器端socket:使用bind()函数将socket与服务器端地址(IP地址和端口号)绑定起来。 3. 监听客户端连接请求:使用listen()函数启动服务器端socket监听客户端连接请求。 4. 接受客户端连接请求:使用accept()函数接受来自客户端的连接请求,并创建一个新的socket用于与客户端通信。 5. 接收客户端发送的文件:使用recv()函数从客户端接收文件数据,并写入到服务器端的文件中。 6. 发送响应给客户端:使用send()函数向客户端发送一个响应,表示文件传输完成。 7. 关闭服务器端socket:使用close()函数关闭服务器端socket。 客户端的步骤如下: 1. 创建客户端socket:使用socket()函数创建一个新的socket,指定协议族(AF_INET表示IPv4)和socket类型(SOCK_STREAM表示流式套接字)。 2. 连接服务器端:使用connect()函数连接到服务器端,指定服务器端地址(IP地址和端口号)。 3. 打开要发送的文件:使用fopen()函数打开要发送的文件。 4. 发送文件数据给服务器端:使用send()函数将文件数据发送给服务器端。 5. 接收服务器端的响应:使用recv()函数接收服务器端的响应,判断文件是否传输完成。 6. 关闭客户端socket:使用close()函数关闭客户端socket。 以上是基于TCP服务器端/客户端的基本步骤,您可以根据自己的需求进行修改和扩展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值