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

发布于:2025-08-15 ⋅ 阅读:(15) ⋅ 点赞:(0)

一、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;
}


网站公告

今日签到

点亮在社区的每一天
去签到