首页 > C语言网络编程:TCP客户端实现

C语言网络编程:TCP客户端实现

文章目录

          • 客户端通信步骤
          • 为什么客户端没有bind和listen
          • 客户端connect函数介绍
          • 局域网内客户端和服务器通信代码实例

客户端通信步骤

根据基本TCP网络通信编程模型

在这里插入图片描述

我们可以知道客户端的实现主要有几个步骤

  1. socket创建客户端通信的套接字文件,并指定通信的协议族和数据类型
  2. 使用connect主动向服务器发起连接请求,与服务器的accept实现三次握手建立连接。连接成功之后客户端可以通过socket返回的文件描述符进行通信 ,服务器使用accept返回的通信文件描述符进行通信
  3. 使用send/recv和服务器 发送接收数据进行通信
  4. 通信结束之后断开连接close或者shutdown
为什么客户端没有bind和listen

关于bind函数的功能,是为了绑定ip和端口号,即通信使用固定的ip和端口号进行通信。这样的要求仅仅是针对tcp服务器而言的,对于多个客户端来说,这样的bind操作是不需要的。如果指定bind也是可以通信,但是并不合理。在通信过程中只需要使用自己的ip和默认分配的端口号即可。客户端默认分配的端口范围为49152~65535

关于listen函数,对于客户端程序,它永远是主动连接请求的发起者,没有被动监听别人连接的需求。所以这里不需要将主动通信文件描述符转换为被动通信文件描述符。

客户端connect函数介绍
  • 头文件:#include
  • 函数使用:int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
  • 函数功能: 向服服务器发起主动连接请求
  • 返回值:成功0;失败 -1,并设置errno。
  • 函数参数:

    a. sockfd socket返回的套接字文件描述符

    b. addrlen addr结构体的大小

    c. addr 用于设置想要连接的服务器的ip和端口号

    注意:如果局域网内部通信,则ip和端口号可以直接填写服务器的ip和端口号,但是如果是跨网通信,IP必须是服务器所在路由器的公网ip

    为了方便设置struct sockaddr结构体的内容,我们同样使用sockaddr_in结构体进行设置
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(6789);
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");cfd = connect(sockfd,(struct sockaddr *)&addr,sizeof(addr));
    
局域网内客户端和服务器通信代码实例

一下代码是在本机测试,大家可以测试局域网内以及不同网段的通信,不过需要注意IP地址的填写,跨网段通信中ip的填写需要保证是服务器所在路由器的公网ip。

server.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include #define IP "192.168.102.174"
#define PORT 7000typedef struct data { char name[30];unsigned int num;
}Data;
void print_err(char *str, int line, int err_no) { printf("%d, %s :%s
",line,str,strerror(err_no));_exit(-1);
}
int cfd = -1;//子线程,处理数据的接收
void *receive(void *pth_arg) { int ret = 0;Data stu_data = { 0};while(1) { bzero(&stu_data, sizeof(stu_data));ret = recv(cfd, &stu_data, sizeof(stu_data),0);	if (-1 == ret) { print_err("recv failed",__LINE__,errno);}else if (ret > 0)printf("student number = %d student name = %s 
",ntohl(stu_data.num),stu_data.name);}
}//接收SIGINT信号,从而关闭通信描述符。
void sig_fun(int signo) { if (signo == SIGINT) { //close(cfd);shutdown(cfd,SHUT_RDWR);_exit(0);}
}int main()
{ int skfd = -1, ret = -1;skfd = socket(AF_INET, SOCK_STREAM, 0);if ( -1 == skfd) { print_err("socket failed",__LINE__,errno);}struct sockaddr_in addr;addr.sin_family = AF_INET; //设置tcp协议族addr.sin_port = htons(PORT); //设置端口号addr.sin_addr.s_addr = inet_addr(IP); //设置ip地址ret = bind(skfd, (struct sockaddr*)&addr, sizeof(addr));if ( -1 == ret) { print_err("bind failed",__LINE__,errno);}ret = listen(skfd, 3);if ( -1 == ret ) { print_err("listen failed", __LINE__, errno);}/*被动监听客户端发起的tcp连接请求,三次握手后连接建立成功*/struct sockaddr_in caddr = { 0};int csize = sizeof(caddr);cfd = accept(skfd, (struct sockaddr*)&caddr, &csize);if (-1 == cfd) { print_err("accept failed", __LINE__, errno);}//打印连接后接收到的客户端发送过来的ip和端口号printf("cport = %d, caddr = %s
", ntohs(caddr.sin_port),inet_ntoa(caddr.sin_addr));创建子线程用于接收数据pthread_t id;ret = pthread_create(&id,NULL,receive,NULL);if (-1 == ret) print_err("pthread_create failed", __LINE__, errno);//用于处理信号函数,处理SIGINT的信号signal(SIGINT,sig_fun);Data std_data = { 0};while (1) { bzero(&std_data, sizeof(std_data));printf("stu name:
");scanf("%s",std_data.name);//对于int型的需要将主机端序转换为网络端序,这里转成long型。printf("stu num:
");scanf("%d",&std_data.num);std_data.num = htonl(std_data.num);将数据std_data强制类型转换后发送ret = send(cfd, (void *)&std_data,sizeof(std_data),0);if ( -1 == ret) { print_err("accept failed", __LINE__, errno);}	}return 0;
}

client.c

客户端的代码和服务器端主要差异在连接上,客户端只需要初始化一下socket即可主动发起连接请求,服务端在初始化socket之后需要绑定ip和端口号,同时将主动通信的文件描述符转为被动通信的文件描述符。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include #define IP "192.168.102.174"
#define PORT 7000typedef struct data { char name[30];unsigned int num;
}Data;
void print_err(char *str, int line, int err_no) { printf("%d, %s :%s
",line,str,strerror(err_no));_exit(-1);
}
int skfd = -1;
void *receive(void *pth_arg) { int ret = 0;Data stu_data = { 0};while(1) { bzero(&stu_data, sizeof(stu_data));ret = recv(skfd, &stu_data, sizeof(stu_data),0);	if (-1 == ret) { print_err("recv failed",__LINE__,errno);}else if (ret > 0)printf("student number = %d student name = %s 
",ntohl(stu_data.num),stu_data.name);}
}void sig_fun(int signo) { if (signo == SIGINT) { //close(cfd);shutdown(skfd,SHUT_RDWR);_exit(0);}
}int main()
{ int ret = -1;skfd = socket(AF_INET, SOCK_STREAM, 0);if ( -1 == skfd) { print_err("socket failed",__LINE__,errno);}/*这里需要填写服务器的端口号和IP地址,此时客户端会自己分配端口号和自己的IP发送给服务器*/struct sockaddr_in addr;addr.sin_family = AF_INET; //设置tcp协议族addr.sin_port = htons(PORT); //设置服务器端口号addr.sin_addr.s_addr = inet_addr(IP); //设置服务器ip地址,如果是跨网,则需要填写服务器所在路由器的公网ipret = connect(skfd,(struct sockaddr *)&addr,sizeof(addr));if ( -1 == ret ) { print_err("connect failed", __LINE__, errno);}pthread_t id;pthread_create(&id,NULL,receive,NULL);Data std_data = { 0};while (1) { bzero(&std_data, sizeof(std_data));printf("stu name:
");scanf("%s",std_data.name);printf("stu num:
");scanf("%d",&std_data.num);std_data.num = htonl(std_data.num);ret = send(skfd, (void *)&std_data,sizeof(std_data),0);if ( -1 == ret) { print_err("send failed", __LINE__, errno);}	}return 0;
}

编译gcc server.c -o server -pthread

gcc client.c -o client -pthread

运行如下:

在这里插入图片描述

更多相关:

  • 关于点云的分割算是我想做的机械臂抓取中十分重要的俄一部分,所以首先学习如果使用点云库处理我用kinect获取的点云的数据,本例程也是我自己慢慢修改程序并结合官方API 的解说实现的,其中有很多细节如果直接更改源程序,可能会因为数据类型,或者头文件等各种原因编译不过,会导致我们比较难得找出其中的错误,首先我们看一下我自己设定的一个场景,...

  • /* 使用正态分布变换进行配准的实验 。其中room_scan1.pcd room_scan2.pcd这些点云包含同一房间360不同视角的扫描数据 */ #include #include #include #include

  • #include #include #include #include ...

  • #include #include #include #include #include #include...

  • #include #include #include #include int main (int argc,...

  • 学习计划 MyPlan11 主题:Python描述统计、简单统计图形 时间:8.5-8.11周内完成 参考资料:新书《谁说菜鸟不会数据分析python篇》 各位星友们,在这个星球里每个人都要逼迫自己学习未知的领域或知识点,每天进步一点点,积累的时间久了 ,菜鸟也能起飞。 完成情况: 在pandas中,使用describe函数进行描述统...

  • 利用SocketServer模块来实现网络客户端与服务器并发连接非阻塞通信。 首先,先了解下SocketServer模块中可供使用的类: BaseServer:包含服务器的核心功能与混合(mix-in)类挂钩;这个类只用于派生,所以不会生成这个类的实例;可以考虑使用TCPServer和UDPServer。 TCPServer/UDPS...

  • 题目:序列化二叉树 请实现两个函数,分别用来序列化和反序列化二叉树。 示例:  你可以将以下二叉树:     1    /   2   3      /     4   5 序列化为 "[1,2,3,null,null,4,5]" 解题: /*** Definition for a binary tree no...

  • sd.js  import $global from "./global"; import $g from "./sg"; import $ from "jquery"; import {Message, Loading} from "element-ui";//引入饿了么相关组件 import {Base64} from "js-...

  •     原生sd.js----------------------------------------------------------------  const API_ROOT_URL = "http://www.api.com";const $d= {_postList_url: API_ROOT_URL + "/api...