当前位置: 首页 > news >正文

TCP客户端Linux网络编程设计详解

一、TCP 客户端设计流程

        TCP客户端模式的程序设计流程主要分为:套接字初始化( socket()函数),连接目标网络服务器 (connect()函数),向服务器端写入数据(write()函数)

1、socket() 函数

#include <sys/types.h>     /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);

        socket()函数函数建立一个协议族为domain、协议类型为type、协议编号为 protocol的套接字文件描述符。如果函数调用成功,会返回一个表示这个套接字的文件描述符,失败的时候返回-1。

        函数socket()的参数domain用于设置网络通信的域,函数socket()根据这个参数选择通信协议的族。通信协议族在文件sys/socket.h 中定义。

        函数socket()的参数 type用于设置套接字通信的类型。主要有SOCK_STREAM(流式套接字)、 SOCK_DGRAM(数据包套接字)等。
函数socket()的参数protocol一般设置为0。
        函数socket()并不总是执行成功,有可能会出现错误,错误的产生有多种原因,可以通过errno获 得。通常情况下造成函数 socket()失败的原因是输入的参数错误造成的,例如某个协议不存在等, 这时需要详细检查函数的输入参数。由于函数的调用不一定成功,在进行程序设计的时候,一定要检查返回值。
如果进行TCP编程,我们可以使用如下代码建立一个流式套接字:
int sock_fd = socket(AF_INET, SOCK_STREAM, 0);

2、connect() 函数原型

#include <sys/types.h>  /* See NOTES */
#include <sys/socket.h>
int connect(int sockfd,  const struct sockaddr *addr,  socklen_t addrlen);
        参数sockfd是建立套接字时返回的套接字文件描述符,它是由系统调用socket()函数返回的。参数addr,是一个指向数据结构 sockaddr的指针,其中包括客户端需要连接的服务器的目的端口和IP地 址,以及协议类型。参数addrlen表示第二个参数内容的大小,可以使用sizeof(struct sockaddr)而 获得,与bind()函数不同,这个参数是一个整型的变量而不是指针。
connect()函数的返回值在成功时为0,当发生错误的时候返回-1,可以查看errno获得错误的原因。

(1)包裹函数

//connect包裹函数
void Connect(int fd, const struct sockaddr*addr,socklen_t addrlen)
{int ret;while (1){ret = connect(fd, addr, addrlen);if (-1 == ret){dbgout("connect error\n");perror("eror");continue;}break;}
}

3、数据的发送

1) write()函数用于发送数据,函数原型如下:

#include <sys/types.h>
#include <sys/socket.h>

 ssize_t  write(int sockfd, const void *buf, size_t len);

参数:

        sockfd:TCP Socket 描述符。

        buf:要发送的数据缓冲区。

        count:要发送的字节数。

示例代码:

write(fd, "hello", sizeof("hello"));

2) send()函数, 原型如下:

#include <sys/types.h>
#include <sys/socket.h>

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

参数:

        sockfd:TCP Socket 描述符。

        buf:要发送的数据缓冲区。

        len:要发送的字节数。

        flags:可选的标志参数,用于控制发送行为,如 MSG_DONTWAIT、MSG_NOSIGNAL 等。

示例代码:

send(fd, "hello", sizeof("hello"), MSG_DONTWAIT)

4、数据的接收

读取客户端的数据可以使用以下三个函数:

1)read() 数据接收函数

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);

         read()函数从套接字sockfd中接收数据放到缓冲区buf中,buf的长度为len。 第1个参数sockfd是套接口文件描述符,它是由系统调用socket()返回的。第2个参数buf是一个指针, 指向接收网络数据的缓冲区。第3个参数len表示接收缓冲区的大小,以字节为单位。

使用示例代码:

char buf[1024];
//读取数据
memset(buf, 0, sizeof(buf));
int ret;
ret = read(fd, buf, sizeof(buf)-1); //阻塞
//如果主动断开了连接,read函数解除阻塞,并且返回0
if (0 == ret)
{close(cli_fd);return NULL;
}printf("cli: %d,  %s\n", cli_fd, buf)

2)recv() 数据接收函数

recv()函数用于接收数据,函数原型如下:

#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);

前三个跟read函数一致,recv()函数的参数flags用于设置接收数据的方式,可选择的值及含义在下表中列出(flags 的值可以是表中值的按位或生成的复合值)

上表中的值的具体说明:

MSG_DONTWAIT:这个标志将单个IO操作设为非阻塞方式,而不需要在套接字 上打开非阻塞标志,执行IO操作,然后关闭非阻塞标志。

MSG_ERRQUEUE:该错误的传输依赖于所使用的协议。

MSG_OOB:这个标志可以接收带外数据,而不是接收一般数据。

MSG_PEEK:这个标志用于查看可读的数据,在recv()函数执行后,内核不会将这些数据丢弃。 MSG_TRUNC:在接收数据后,如果用户的缓冲区大小不足以完全复制缓冲区中的数据,则将 数据截断,仅靠复制用户缓冲区大小的数据。其他的数据会被丢弃。

MSG_WAITALL:这个标志告诉内核在没有读到请求的字节数之前不使读操作返回。

示例代码:

char buf[1024];
//读取数据
memset(buf, 0, sizeof(buf));
int ret;
ret = recv(cli_fd, buf, sizeof(buf)-1,MSG_DONTWAIT); 
if (0 == ret)
{close(cli_fd);return NULL;
}printf("cli: %d,  %s\n", cli_fd, buf)

3)recvfrom() 数据接收函数

ssize_t recvfrom(int sockfd,  void *buf,  size_t len,  int flags, struct sockaddr *src_addr, socklen_t *addrlen)

        第四个参数保存接收的客户端的相关信息,但是因为TCP是基于连接的传输协议,在调用accept函数 时就能够获取客户端的信息,所以recvfrom一般用于udp通信。

7、示例TCP客户端编程代码:

#include <stdio.h>
#include <sys/types.h>        
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>#define dbgout(arg...) \do{ \char b__[1024]; \sprintf(b__, arg);\fprintf(stdout, "[%s,%s,%d] %s", __FILE__, __func__, __LINE__, b__); \} while (0)//connect包裹函数
void Connect(int fd, const struct sockaddr*addr,socklen_t addrlen)
{int ret;while (1){ret = connect(fd, addr, addrlen);if (-1 == ret){dbgout("connect error\n");perror("eror");continue;}break;}
}int main(int argc, char* argv[])
{//初始化一个socket套接字int fd;fd = socket(AF_INET, SOCK_STREAM, 0);if (-1 == fd){dbgout("socket error\n");perror("erro");return 0;}//连接服务器struct sockaddr_in serv;serv.sin_family = AF_INET;serv.sin_addr.s_addr = inet_addr("172.29.98.213");serv.sin_port = htons(8888);//和服务器建立连接Connect(fd, (struct sockaddr*)&serv, sizeof(struct sockaddr));char buf[128];while (1){//向服务端发送数据write(fd, "hello", 6);//读取服务端发送的数据memset(buf, 0, sizeof(buf));int ret = read(fd, buf, sizeof(buf) - 1);if (ret > 0){dbgout("read: %s\n", buf);}sleep(1);}close(fd);return 0;
}

http://www.lryc.cn/news/620329.html

相关文章:

  • 人工智能——CNN基础:卷积和池化
  • HiSmartPerf使用WIFI方式连接Android机显示当前设备0.0.0.0无法ping通!设备和电脑连接同一网络,将设备保持亮屏重新尝试
  • SAP Valuation Category在制造业成本核算中的使用场景与配置方案
  • 基于C语言基础对C++的进一步学习_C和C++编程范式、C与C++对比的一些补充知识、C++中的命名空间、文件分层
  • window显示驱动开发—多平面覆盖 VidPN 呈现
  • 看懂 Linux 硬件信息查看与故障排查
  • 力扣42:接雨水
  • 人工智能入门①:AI基础知识(上)
  • Python图像处理基础(十三)
  • 《工程封装》(Python)
  • 网络安全合规6--服务器安全检测和防御技术
  • 3.Ansible编写和运行playbook
  • 3DM游戏运行库合集离线安装包下载, msvcp140.dll丢失等问题修复
  • ESP32_STM32_DHT20
  • 三极管的基极为什么需要下拉电阻
  • Vue3从入门到精通:4.1 Vue Router 4深度解析与实战应用
  • vue实现模拟 ai 对话功能
  • JS的学习5
  • vue修改element的css属性
  • 决策树回归:用“分而治之”的智慧,搞定非线性回归难题(附3D可视化)
  • 北京JAVA基础面试30天打卡09
  • uniapp授权登录
  • 硬件工程师八月实战项目分享
  • 8.13迎来联动:PUBG布加迪,新版本37.1内容资讯!低配置也能飙车吃鸡!
  • 谈一些iOS组件化相关的东西
  • 【Golang】 Context.WithCancel 全面解析与实战指南
  • CAN仲裁机制的原理
  • 【CV 目标检测】③——目标检测方法
  • 玳瑁的嵌入式日记D17-08013(linux文件编程)
  • 深度学习(5):激活函数