【Linux系统化学习】网络套接字(编写简单的TCP服务端和客户端)

本文主要是介绍【Linux系统化学习】网络套接字(编写简单的TCP服务端和客户端),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

TCP服务端

创建套接字

解决绑定失败问题

填充网络信息

绑定

设置监听状态

接受请求

收取和反馈消息

完整的服务端封装代码

TCP客户端

创建套接字

填充网络信息

发起连接

发送和收取消息

客户端完整代码

 一些补充


TCP服务端

初始化服务端

创建套接字

和UDP创建套接字的方式差不多,只不过我们要实现的是TCP第二个参数选用:SOCK_STREAM

解决绑定失败问题

服务端关闭绑定失败的问题通常出现在服务端程序退出后,重新启动时,可能会因为之前的套接字仍然处于 TIME_WAIT 状态,导致无法绑定到相同的地址和端口。解决这个问题的方法通常是通过设置 SO_REUSEADDRSO_REUSEPORT 选项。

int opt = 1;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));

填充网络信息

和UDP填充本地网络信息的方式一样,还是要注意以下几点:

  • 端口号主机序列转网络序列
  • IP地址点分十进制字符串转四字节网络序列
  • 设置任意的IP地址

绑定

和UDP绑定一样,要注意结构体的强转

设置监听状态

因为TCP要建立连接,一般是由客户端发起请求,服务端等待请求的到来。所以服务端要设置监听状态

int listen(int sockfd, int backlog);

返回值

listen() 函数的返回值是一个整数,表示执行结果的状态:

  • 如果成功,返回值为 0。
  • 如果失败,返回值为 -1,并且设置全局变量 errno 来指示具体的错误类型。

参数

  • sockfd:是一个已经通过 socket() 函数创建的套接字描述符,即要进行监听的套接字。

  • backlog:是一个整数,指定在内核中等待处理的连接队列的最大长度。这个参数的具体含义是系统内核在拒绝新连接之前允许处于未连接状态(SYN_RCVD)的连接数量。

    如果队列满了,新的连接会被拒绝,并且客户端可能会收到 ECONNREFUSED 错误。较大的 backlog 值可以容纳更多的等待连接的客户端,但同时也会增加系统资源的消耗。

    如果 backlog 设置为 0,表示不接受连接队列,新连接会立即被拒绝。

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

启动服务端

接受请求

accept() 函数用于接受客户端的连接请求,并创建一个新的套接字来处理该连接。这个新的套接字是与客户端建立的连接的专用套接字,通过它可以进行数据的收发。

返回值

accept() 函数的返回值是一个整数,表示新创建的套接字的文件描述符:

  • 如果成功,返回值是新创建的套接字的文件描述符。
  • 如果失败,返回值为 -1,并且设置全局变量 errno 来指示具体的错误类型。

函数调用成功后的返回值是作为我们接下来进行读取数据的文件描述符,旧的套接字依旧作为监听套接字。 

参数

  • sockfd:是一个已经通过 socket() 函数创建并调用 bind() 函数绑定了地址的监听套接字描述符,即待处理连接的监听套接字。

  • addr:是一个指向 struct sockaddr 类型的指针,用于存储客户端的地址信息。这个参数是一个输出参数,accept() 函数会填写客户端的地址信息到这个结构体中。如果不需要知道客户端的地址,可以将这个参数设置为 NULL

  • addrlen:是一个指向 socklen_t 类型的指针,用于指定 addr 结构体的长度。在调用 accept() 函数之前,需要将 addrlen 设置为 addr 结构体的长度。这个参数也是一个输入输出参数,accept() 函数会将实际的客户端地址长度写入到这个变量中。

收取和反馈消息

因为建立连接后得到一个文件描述符,因此我们可以用使用文件的读写方法来进行我们的收取和反馈消息的操作。


完整的服务端封装代码

 TCPserver.hpp

#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <strings.h>
#include <cerrno>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
using namespace std;
const static int default_backlog = 5;
class TcpServer
{
public:TcpServer(uint16_t port): _port(port), _isrunning(false){}// 初始化服务器void Init(){// 第一步:套接字创建// 得到文件描述符// 本质是文件// 第一个参数表示域// 第二个参数表示套接字类型// 第三个参数为协议 默认缺省_listensock = socket(AF_INET, SOCK_STREAM, 0);if (_listensock < 0){cout << "Fatal Error" << endl<< "create socket error, errno code %d ,eror string %d",errno, strerror(errno);cout << endl;exit(2);}cout << "create socket success, sockfd : " << _listensock << endl;int opt=1;setsockopt(_listensock,SOL_SOCKET,SO_REUSEADDR|SO_REUSEPORT,&opt,sizeof(opt));//解决服务端关闭绑定失败问题// 第二步:填充本地网络信息并且绑定和监听struct sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(_port);local.sin_addr.s_addr = INADDR_ANY;// 设置内核空间 ——绑定if (bind(_listensock, (struct sockaddr *)&local, sizeof(local)) != 0){cout << "bind Error" << "errno code : " << errno << "error string : " << strerror(errno) << endl;exit(3);}cout << "bind success" << endl;// Tcp需要建立连接,一般是由客户端发起请求 服务端一直等待连接的到来// Tcp需要监听// 设置套接字为监听状态if (listen(_listensock, default_backlog) != 0){cout << "listen Error" << "errno code: " << errno << "error string : " << strerror(errno) << endl;exit(4);}cout << "listen success " << endl;}void Sverse(int fd){char buffer[1024];while (1){// 读取数据// tcp是面向字节流和文件管道差不多// 直接使用文件的读写方法即可ssize_t n = read(fd, buffer, sizeof(buffer) - 1);if (n > 0){// 对同一个fd进行读写buffer[n] = 0;cout << "client say#" << buffer << endl;string echo_string = "client say#";echo_string += buffer;write(fd, echo_string.c_str(), echo_string.size());}else if (n == 0) // 如果返回值为0 ,代表读到了文件结尾 (对端关闭了连接){cout << "client quit..." << endl;break;}else{cout << "read Error" << "error code : " << errno << "error string : " << strerror(errno) << endl;break;}}}void Start(){// 服务器启动了_isrunning = true;while (1){// 先获取连接// 后两个参数为输入输出型参数//等同于UDP————recvfrom// 返回值 成功了返回一个非零的新的文件描述符(网络套接字) 失败了返回-1//struct sockaddr_in peer;socklen_t len = sizeof(peer);int sockfd = accept(_listensock, (struct sockaddr *)&peer, &len);if (sockfd < 0){cout << "accept Error" << "error code: " << errno << "error string: " << strerror(errno) << endl;// 监听失败继续监听continue;}cout << "accept success , get a new sockfd : " << sockfd << endl;// 提供服务Sverse(sockfd);close(sockfd);}}~TcpServer(){if (_listensock > 0){close(_listensock);}}private:// 端口号uint16_t _port;int _listensock;bool _isrunning;
};

Main.cc

#include<iostream>
#include<memory>
#include"TcpServer.hpp"
using namespace std;
void Usage(const string & process)
{cout<<"Usage:"<<endl<<process <<" local_port"<<endl;
}
int main(int argc ,char * argv[])
{if(argc!=2){Usage(argv[0]);return 1;}uint16_t port = stoi(argv[1]);auto* tsvr = new TcpServer(port);tsvr->Init();tsvr->Start();delete tsvr;return 0;
}

TCP客户端

创建套接字

和服务端的一样

填充网络信息

和服务端的一样

发起连接

connect() 函数用于客户端向服务器发起连接请求。它在客户端程序中使用,用于连接到远程服务器。

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

返回值

connect() 函数的返回值是一个整数,表示执行结果的状态:

  • 如果成功,返回值为 0。
  • 如果失败,返回值为 -1,并且设置全局变量 errno 来指示具体的错误类型。

 参数

  • sockfd:是一个已经通过 socket() 函数创建的套接字描述符,即待连接的套接字。

  • addr:是一个指向 struct sockaddr 类型的指针,用于存储远程服务器的地址信息。通常是使用 struct sockaddr_instruct sockaddr_in6 结构体来表示 IPv4 或 IPv6 地址。这个参数包含了远程服务器的 IP 地址和端口号。

  • addrlen:是一个 socklen_t 类型的整数,表示 addr 结构体的长度。

注:这个参数调用成功操作系统会自动绑定。 

发送和收取消息

因为socket的本质也是一个文件描述符,因此我们可以像上面一样使用文件的读写方法来发送和收取消息。


客户端完整代码

#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
using namespace std;
bool visitserver(string &serverip, uint16_t serverport, int *cnt)
{// 创建套接字int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0){cout << "client sockfd error" << endl;return false;}bool ret = true;// 客户端不用绑定 , 客户端必须要有服务端的IP和port , 需要绑定,但是不需要用户显示绑定,因为client系统会随机绑定端口// Tcp发起连接的时候,client会被操作系统自动绑定// 发起连接struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(serverport);// 字符串的点分十进制转为四字节inet_pton(AF_INET, serverip.c_str(), &server.sin_addr);int n = connect(sockfd, (struct sockaddr *)&server, sizeof(server)); // 自动进行绑定// 连接失败if (n < 0){cout << "connect error" << endl;ret = false;goto END;}// 重连成功,重连次数清零*cnt = 0;// 进行通信while (1){string inbuffer;cout << "Plase Enter";getline(cin, inbuffer);if (inbuffer == "quit")break;ssize_t n = write(sockfd, inbuffer.c_str(), inbuffer.size());if (n > 0){char buffer[1024];ssize_t m = read(sockfd, buffer, sizeof(buffer) - 1);if (m > 0){buffer[m] = 0;cout << "get a echo message ->" << buffer << endl;}else if (m == 0){break;}else{ret = false;goto END;}}else{ret = false;goto END;}}
END:close(sockfd);return ret;
}
void Usage(const string &process)
{cout << "Usage : " << process << " server_ip server_port" << endl;
}
int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]);return 1;}string serverip = argv[1];uint16_t serverport = stoi(argv[2]);// 定制重连int cnt = 1;while (cnt <= 5){bool result = visitserver(serverip, serverport, &cnt);if (result){cnt = 5;break;}else{sleep(1);cout << "server offline, retrying...,cout : " << cnt++ << endl;cnt++;}}// 大于重连次数if (cnt >= 5){cout << "server offline" << endl;}return 0;
}//

一些补充

uint32_t inet_addr(const char *cp);

这个函数在上篇文章中是用来将点分十进制的字符串IP地址转化为四字节的网络序列的,但是这个函数由于它的实现方法原因是不是线程安全的函数,以后我们尽量使用下面的函数。

int inet_pton(int af, const char *src, void *dst);

返回值

  • 如果转换成功,返回值为 1。
  • 如果输入的地址格式不正确,返回值为 0。
  • 如果发生错误,返回值为 -1,并且设置全局变量 errno 来指示具体的错误类型。

参数

  • af:指定地址族(Address Family),通常是 AF_INET(IPv4)或 AF_INET6(IPv6)。
  • src:指向以字符串形式表示的 IP 地址的指针,即点分十进制格式的 IP 地址。
  • dst:指向用于存储结果的缓冲区的指针,通常是一个 struct in_addr 结构体的指针(IPv4)或 struct in6_addr 结构体的指针(IPv6)。

今天对网络套接字的分享到这就结束了,希望大家读完后有很大的收获,也可以在评论区点评文章中的内容和分享自己的看法;个人主页还有很多精彩的内容。您三连的支持就是我前进的动力,感谢大家的支持!!! 

这篇关于【Linux系统化学习】网络套接字(编写简单的TCP服务端和客户端)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/963710

相关文章

Linux ls命令操作详解

《Linuxls命令操作详解》通过ls命令,我们可以查看指定目录下的文件和子目录,并结合不同的选项获取详细的文件信息,如权限、大小、修改时间等,:本文主要介绍Linuxls命令详解,需要的朋友可... 目录1. 命令简介2. 命令的基本语法和用法2.1 语法格式2.2 使用示例2.2.1 列出当前目录下的文

使用Python开发一个简单的本地图片服务器

《使用Python开发一个简单的本地图片服务器》本文介绍了如何结合wxPython构建的图形用户界面GUI和Python内建的Web服务器功能,在本地网络中搭建一个私人的,即开即用的网页相册,文中的示... 目录项目目标核心技术栈代码深度解析完整代码工作流程主要功能与优势潜在改进与思考运行结果总结你是否曾经

Linux中的计划任务(crontab)使用方式

《Linux中的计划任务(crontab)使用方式》:本文主要介绍Linux中的计划任务(crontab)使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、前言1、linux的起源与发展2、什么是计划任务(crontab)二、crontab基础1、cro

Linux换行符的使用方法详解

《Linux换行符的使用方法详解》本文介绍了Linux中常用的换行符LF及其在文件中的表示,展示了如何使用sed命令替换换行符,并列举了与换行符处理相关的Linux命令,通过代码讲解的非常详细,需要的... 目录简介检测文件中的换行符使用 cat -A 查看换行符使用 od -c 检查字符换行符格式转换将

Linux系统配置NAT网络模式的详细步骤(附图文)

《Linux系统配置NAT网络模式的详细步骤(附图文)》本文详细指导如何在VMware环境下配置NAT网络模式,包括设置主机和虚拟机的IP地址、网关,以及针对Linux和Windows系统的具体步骤,... 目录一、配置NAT网络模式二、设置虚拟机交换机网关2.1 打开虚拟机2.2 管理员授权2.3 设置子

揭秘Python Socket网络编程的7种硬核用法

《揭秘PythonSocket网络编程的7种硬核用法》Socket不仅能做聊天室,还能干一大堆硬核操作,这篇文章就带大家看看Python网络编程的7种超实用玩法,感兴趣的小伙伴可以跟随小编一起... 目录1.端口扫描器:探测开放端口2.简易 HTTP 服务器:10 秒搭个网页3.局域网游戏:多人联机对战4.

Linux系统中卸载与安装JDK的详细教程

《Linux系统中卸载与安装JDK的详细教程》本文详细介绍了如何在Linux系统中通过Xshell和Xftp工具连接与传输文件,然后进行JDK的安装与卸载,安装步骤包括连接Linux、传输JDK安装包... 目录1、卸载1.1 linux删除自带的JDK1.2 Linux上卸载自己安装的JDK2、安装2.1

Mysql表的简单操作(基本技能)

《Mysql表的简单操作(基本技能)》在数据库中,表的操作主要包括表的创建、查看、修改、删除等,了解如何操作这些表是数据库管理和开发的基本技能,本文给大家介绍Mysql表的简单操作,感兴趣的朋友一起看... 目录3.1 创建表 3.2 查看表结构3.3 修改表3.4 实践案例:修改表在数据库中,表的操作主要

Linux卸载自带jdk并安装新jdk版本的图文教程

《Linux卸载自带jdk并安装新jdk版本的图文教程》在Linux系统中,有时需要卸载预装的OpenJDK并安装特定版本的JDK,例如JDK1.8,所以本文给大家详细介绍了Linux卸载自带jdk并... 目录Ⅰ、卸载自带jdkⅡ、安装新版jdkⅠ、卸载自带jdk1、输入命令查看旧jdkrpm -qa

Linux samba共享慢的原因及解决方案

《Linuxsamba共享慢的原因及解决方案》:本文主要介绍Linuxsamba共享慢的原因及解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux samba共享慢原因及解决问题表现原因解决办法总结Linandroidux samba共享慢原因及解决