TCP/IP网络编程(1)——基于TCP的服务端和客户端的简单实现

这篇具有很好参考价值的文章主要介绍了TCP/IP网络编程(1)——基于TCP的服务端和客户端的简单实现。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

前言

一、服务器端函数

1. 创建套接字函数 socket

2. 套接字绑定地址函数 bind

3. 等待连接请求函数 listen

4. 处理连接请求函数 accept

5. 关闭套接字函数 close

二、客户端函数

1. 请求连接函数 connect

三、完整代码

四、 基于TCP的半关闭 shutdown

五、 套接字可选项 getsockopt & setsockopt

 


前言

本系列是阅读尹圣雨所著TCP/IP网络编程一书的学习笔记,我将记录一些关键知识和遇到的问题,在最后能够自己搭建一个简易的服务器。

本文主要介绍TCP服务端和客户端的一些关键函数


一、服务器端函数

1. 创建套接字函数 socket

int socket(int domain, int type, int protocol);

调用成功则返回文件描述符,失败则返回-1。为了方便程序员指定套接字,套接字创建时操作系统会自动给套接字分配一个整数,这个整数就是文件描述符。0、1、2是标准输入输出的文件描述符。

(1)domain: 套接字中使用的协议族。常用的有PF_INET(ipv4协议族)、PF_INET6(ipv6协议族)。协议族的类型将决定protocol参数类型的范围。

(2)type: 数据传输类型。主要有SOCK_STREAM(流套接字、TCP),SOCK_DGRAM(数据报套接字、UDP)SOCK_RAW(原始套接字、用于其他协议)三种。

(3)protocol: 通信使用的协议。一般参数为0,除非domain和type都相同但是protocol不同的情况下才需要具体指定。

创建一个TCP套接字如下:

int server_socket = socket(PF_INET,SOCK_STREAM,0);

2. 套接字绑定地址函数 bind

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

调用成功返回0,否则返回-1。

(1)sockfd:套接字的文件描述符。

(2)addr:保存地址信息的结构体变量的地址。

(3)addrlen:结构体变量长度。一般直接用sizeof(addr)。

定义addr结构体并赋值如下:

struct sockaddr_in server_addr;
memset(&server_addr,0,sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr(server_ip);
server_addr.sin_port = htons(atoi(server_port));

结构体struct sockaddr_in中有四个变量:

sin_family,保存地址族信息,常用的有AF_INET(ipv4)、AF_INET6(ipv6);

sin_addr.s_addr,保存32位IP地址,inet_addr()函数将点分十进制的字符串IP地址转化为整数型,同时进行网络字节序转换。 为了统一网络传输数据的顺序,规定采用大端序传输,所以字段要经过网络字节序转换。

sin_port,保存16位端口号,htons()函数用于网络字节序转换,htons意为host to network short(用于16位端口),htonl意为host to network long(用于32位地址)。

sin_zero[8],不使用,必须填充0。所以使用memset()函数给结构体赋初值0。

调用bind函数如下:

bind(server_socket, (struct sockaddr* )&server_addr, sizeof(server_addr));

为什么bind函数第二个参数要求sockaddr结构体,却传入 sockaddr_in结构体?

因为sockaddr结构体将所有信息全部保存在一个数组中,不方便进行赋值。

3. 等待连接请求函数 listen

int listen(int sock, int backlog);

调用成功返回0,失败返回-1。

(1)sock:监听套接字的文件描述符。

(2)backlog:连接请求等待队列的长度。当客户端发起连接请求时会进入连接请求等待队列,服务端通过队列的顺序依次进行连接。

调用listen函数如下:

listen(server_socket,5);

4. 处理连接请求函数 accept

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

调用成功返回套接字文件描述符,失败返回-1。

(1)sock:服务器套接字的文件描述符。

(2)addr:客户端地址信息的结构体变量地址。函数会自动计算连接的客户端的地址信息保存在此结构体变量中。

(3)addrlen:第二个参数长度的变量地址。

调用accept函数如下:

client_socket = accept(server_socket,(struct sockaddr*)&client_addr,&client_len);

5. 关闭套接字函数 close

int close(int sockfd);

服务端在accept后会创建一个clientsocket和客户端的clientsocket相连接,所以客户端和服务端都要close这个clientsocket。

二、客户端函数

1. 请求连接函数 connect

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

 调用成功返回0,失败返回-1。

(1)sock:客户端套接字文件描述符

(2)addr:保存服务器端地址信息的结构体变量的地址。在客户端代码中,同样要进行服务端地址的初始化。

(3)addrlen:结构体变量长度。

三、完整代码

使用上述函数编写一个简单的TCP客户端和服务端,客户端发送一个字符串,服务端接收并打印,客户端输入q则断开连接。

服务端代码:

#include<iostream>
#include<string.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<unistd.h>
using namespace std;
int main()
{
    char buf[1024];

    int server_socket;
    int client_socket;
    server_socket = socket(PF_INET,SOCK_STREAM,0);
    if(server_socket == -1)
    cout << "socket error" << endl;

    char* server_ip = "127.0.0.1";
    char* server_port = "9955";
    struct sockaddr_in server_addr;
    struct sockaddr_in client_addr;
    socklen_t client_len = sizeof(client_addr);

    memset(&server_addr,0,sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = inet_addr(server_ip);
    server_addr.sin_port = htons(atoi(server_port));

    if(bind(server_socket,(struct sockaddr*)&server_addr,sizeof(server_addr)) == -1)
    cout << "bind error" <<endl;

    if(listen(server_socket,5) == -1)
    cout << "listen error" << endl;

    client_socket = accept(server_socket,(struct sockaddr*)&client_addr,&client_len);
    if(client_socket == -1)
    cout << "accept false" << endl;
    
    while(recv(client_socket,buf,sizeof(buf),0))  //当客户端close后,发送EOF,退出循环
    {
        send(client_socket,buf,sizeof(buf),0);
        cout << buf << endl;
    }

    close(client_socket);
    close(server_socket);
    return 0;
}

客户端代码:

#include<iostream>
#include<string.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<unistd.h>
using namespace std;

int main()
{
    int client_socket;
    struct sockaddr_in server_addr;
    char* server_ip = "127.0.0.1";
    char* server_port = "9955";
    char buf[1024];

    client_socket = socket(PF_INET,SOCK_STREAM,0);

    memset(&server_addr,0,sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = inet_addr(server_ip);
    server_addr.sin_port = htons(atoi(server_port));

    if(connect(client_socket,(struct sockaddr*)&server_addr,sizeof(server_addr))==-1)
    cout << "connect error" << endl;
    
    int strlen,recvlen;
    while(1)
    {
        cout << "input: ";
        cin >> buf;
        if(!strcmp(buf,"q"))
        break;
        strlen = send(client_socket,buf,sizeof(buf),0);

        recvlen = 0;
        while(recvlen < strlen)
        recvlen += recv(client_socket,&buf[recvlen],sizeof(buf),0);    //确保数据接收完全
    }
    close(client_socket);
    return 0;
}

四、 基于TCP的半关闭 shutdown

int shutdown(int sock, int howto);

调用成功返回0,失败返回-1。

(1)sock:套接字文件描述符

(2)howto:断开方式。SHUT_RD断开输入流、SHUT_WR断开输出流、SHUT_RDWR同时断开输入输出流。

TCP连接建立后,两端点之间会产生两条输出到输入的流,close函数会直接关闭两条流,但是shutdown函数可以选择只关闭其中一条流。比如端点发送完毕数据,则关闭输出流,向另一方发送一个EOF消息,此时不能再发数据了,但是可以继续接收另一方发来的数据。

五、 套接字可选项 getsockopt & setsockopt

上面我使用的套接字都是默认套接字,但是套接字的属性也可以修改。

读取可选项信息函数getsockopt:

int getsockopt(int sock, int level, int optname, 
                void* optval, socklen_t* optlen);

 修改可选项信息函数setsockopt:

int setsockopt(int sock, int level, int optname, 
                const void* optval, socklen_t optlen);

(1)sock:套接字文件描述符

(2)level:可选项协议层。如SOL_SOCKET、IPPROTO_TCP等

(3)optname:可选项名。可选项协议层中包含的选项名

(4)optval:存储选项信息的地址

(5)optlen:optval的字节数

在此我用可选项SO_REUSEADDR举例。

我在运行服务端代码时遇到了一个问题,如果服务端非正常关闭,再次运行服务端时会报错:地址绑定错误。我查看端口状态发现端口正处于time_wait。

这种情况出现的原因是:先发送fin消息的主机在断开连接后会进入time_wait状态。这样如果另一方未收到确认终止连接的包时,可以重传确认包,保证另一方收到确认包正常终止。

但是这个time_wait状态往往会影响我们测试代码,所以可以设置套接字的可选项,让其可以绑定处于time_wait状态的端口号。

函数调用如下:文章来源地址https://www.toymoban.com/news/detail-727607.html

int opt = true;
socklen_t optlen = sizeof(opt);
setsockopt(server_socket,SOL_SOCKET,SO_REUSEADDR,(void* )&opt,optlen);

到了这里,关于TCP/IP网络编程(1)——基于TCP的服务端和客户端的简单实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 《TCP/IP网络编程》阅读笔记--基于TCP的服务器端/客户端

    目录 1--TCP/IP协议栈 2--TCP服务器端默认函数调用顺序 3--TCP客户端的默认函数调用顺序 4--Linux实现迭代回声服务器端/客户端 5--Windows实现迭代回声服务器端/客户端 6--TCP原理 7--Windows实现计算器服务器端/客户端         TCP/IP协议栈共分 4 层,可以理解为数据收发分成了 4 个层

    2024年02月10日
    浏览(59)
  • 《TCP/IP网络编程》阅读笔记--基于UDP的服务器端/客户端

    目录 1--TCP和UDP的主要区别 2--基于 UDP 的数据 I/O 函数 3--基于 UDP 的回声服务器端/客户端 4--UDP客户端Socket的地址分配 5--UDP存在数据边界 6--UDP已连接与未连接的设置 ① TCP 提供的是可靠数据传输服务,而 UDP 提供的是不可靠数据传输服务; ② UDP 在结构上比 TCP 更简洁,其不会

    2024年02月09日
    浏览(58)
  • Socket网络编程(TCP/IP)实现服务器/客户端通信。

    一.前言 回顾之前进程间通信(无名管道,有名管道,消息队列,共享内存,信号,信号量),都是在同一主机由内核来完成的通信。 那不同主机间该怎么通信呢? 可以使用Socket编程来实现。 Socket编程可以通过网络来实现实现不同主机之间的通讯。 二.Socket编程的网络模型如

    2024年02月08日
    浏览(83)
  • 【网络编程】——基于TCP协议实现回显服务器及客户端

    个人主页:兜里有颗棉花糖 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 兜里有颗棉花糖 原创 收录于专栏【网络编程】【Java系列】 本专栏旨在分享学习网络编程的一点学习心得,欢迎大家在评论区交流讨论💌 TCP提供的API主要有两个类 Socket ( 既会给服务器使用也会给客

    2024年02月03日
    浏览(61)
  • 网络编程:编写一个TCP客户端与服务端

    用的系统是Ubuntu。 socket用来创建套接字。这个函数服务端与客户端都要使用。 第一个参数用来制定地址族规范,比如 AF_INET(PF_INET) 表示IPv4地址, AF_INET6(PF_INET6) 表示IPv6地址。 第二个参数用来制定套接字的类型规范,如 SOCK_STREAM 表示面向连接的套接字, SOCK_DGRAM 表示面

    2024年02月01日
    浏览(45)
  • 【网络编程】实现UDP/TCP客户端、服务器

    需要云服务器等云产品来学习Linux的同学可以移步/--腾讯云--/--阿里云--/--华为云--/官网,轻量型云服务器低至112元/年,新用户首次下单享超低折扣。   目录 一、UDP 1、Linux客户端、服务器 1.1udpServer.hpp 1.2udpServer.cc 1.3udpClient.hpp 1.4udpClient.cc 1.5onlineUser.hpp 2、Windows客户端 二、T

    2024年02月06日
    浏览(57)
  • 【Linux | 网络编程】TCP的服务端(守护进程) + 客户端

    上一节,我们用了udp写了一个服务端和客户端之间通信的代码,只要函数了解认识到位,上手编写是很容易的。 本章我们开始编写tcp的服务端和客户端之前通信的代码,要认识一批新的接口,并将我们之前学习的系统知识加进来,做到融会贯通… 代码详情:👉 Gitee 对于TC

    2024年01月16日
    浏览(44)
  • C#实现简单TCP服务器和客户端网络编程

    在C#中进行网络编程涉及许多类和命名空间,用于创建和管理网络连接、传输数据等。下面是一些主要涉及的类和命名空间: System.Net 命名空间: 这个命名空间提供了大部分网络编程所需的类,包括: IPAddress :用于表示IP地址。 IPEndPoint :表示IP地址和端口号的组合。 Socke

    2024年02月11日
    浏览(60)
  • Python网络编程实战:构建TCP服务器与客户端

    Python网络编程实战:构建TCP服务器与客户端 在信息化时代,网络编程是软件开发中不可或缺的一部分。Python作为一种功能强大的编程语言,提供了丰富的网络编程库和工具,使得开发者能够轻松构建各种网络应用。本文将详细介绍如何在Python中进行网络编程,特别是如何使用

    2024年04月15日
    浏览(42)
  • python网络编程:通过socket实现TCP客户端和服务端

    目录 写在开头 socket服务端(基础) socket客户端(基础) 服务端实现(可连接多个客户端)  客户端实现 数据收发效果   近期可能会用python实现一些网络安全工具,涉及到许多关于网络的知识,逃不过的就是最基本的socket。本文将介绍如何通过python自带的socket库实现TCP客户

    2024年03月21日
    浏览(54)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包