您现在的位置是:首页 >其他 >网络编程——TCP编程网站首页其他

网络编程——TCP编程

JiaYu学长 2024-06-17 10:25:01
简介网络编程——TCP编程

流程

在这里插入图片描述
在C语言中进行TCP编程的一般步骤如下:

(1)包含头文件:
在代码中包含必要的头文件,以便使用TCP编程所需的函数和数据类型。通常情况下,你需要包含 <sys/socket.h>、<netinet/in.h> 和 <arpa/inet.h>。

(2)创建套接字:
使用 socket() 函数创建一个套接字,该套接字将用于网络通信。套接字是一个整数值,它表示一个打开的文件描述符,用于在网络上发送和接收数据。

(3)设置地址和端口:
创建一个 struct sockaddr_in 结构体,并设置其中的成员变量,包括地址和端口号。这个结构体用于指定服务器的地址和端口。

(4)绑定套接字
使用 bind() 函数将套接字绑定到指定的地址和端口上。这将使服务器能够监听指定的端口并接受客户端的连接。

(5)监听连接:
使用 listen() 函数开始监听连接请求。这将使服务器进入被动等待状态,等待客户端连接。

(6)接受连接:
使用 accept() 函数接受客户端的连接请求。当有客户端连接到服务器时,accept() 函数将返回一个新的套接字,该套接字用于与客户端进行通信。

(7)通信:
使用 send() 和 recv() 函数发送和接收数据。服务器和客户端都可以使用这些函数来发送和接收数据。

(8)关闭连接:
在通信结束后,使用 close() 函数关闭套接字,释放资源。

这些步骤提供了一个基本的框架来进行TCP编程。你可以根据需要进行适当的修改和扩展。同时还需要处理错误和异常情况,并确保适当地释放资源,以避免内存泄漏和其他问题。

在这里插入图片描述

服务器

(1)socket:创建一个用与链接的套接字(用于链接)

(2)bind:绑定自己的ip地址和端口

(3)listen:监听,将主动套接字转为被动套接字

(4)accept:阻塞等待客户端链接,链接成功返回一个用于通信套接字

(5)recv:接收消息

(6)send:发送消息

(7)close:关闭文件描述符

客户端

(1)socket:创建一个套接字

(2)填充结构体:填充服务器的ip和端口

(3)connect:阻塞等待链接服务器

(4)recv/send:接收/发送消息

(5)close:关闭

函数接口

1、socket

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
int socket(int domain,int type,int protocol);
 功能:创建套接字文件  
 参数:
     domain:协议族 ,选择通信方式
        AF_UNIX, AF_LOCAL   本地通信
	AF_INET             IPv4 
	AF_INET6            IPv6  
    type:通信协议-套接字类型
	SOCK_STREAM   流式套接字
	SOCK_DGRAM    数据报套接字
	SOCK_RAW      原始套接字
    protocol:协议  填0,自动匹配底层TCP或UDP等协议。根据type匹配系统默认自动帮助匹配对应协议
     传输层:IPPROTO_TCP、IPPROTO_UDP、IPPROTO_ICMP
     网络层:htons(ETH_P_IP|ETH_P_ARP|ETH_P_ALL)

返回值:成功。返回同于链接的文件描述符
       失败 -1,更新errno

2、bind

```c
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd,const struct sockaddr* addr,socklen_t addrlen);
 功能:绑定套接字 - ip和端口
 功能:
   sockfd:套接字文件描述符
   addr:用于通信结构体 (提供的是通用结构体,需要根据选择通信方式,填充对应结构体-通信结构体由socket第一个参数确定)

  addrlen:结构体大小
返回值: 成功0
      失败:-1 更新errno

 通用结构体:  
struct sockaddr{
	sa_family_t sa_family;
	char        sa_data[14];
}

 ipv4的通信结构体:
struct sockaddr_in{
	sa_family_t    sin_family;/*AF_INET */
	in_port_t      sin_port;/* 端口 */
	struct in_addr sin_addr;/* ip地址 */
};
struct in_addr{
	uint32_t       s_addr;
};

本地通信结构体:
struct sockaddr_un{
	sa_family_t sun_family;/* AF_UNIX */
	char        sun_path[108];/* 套接字文件 */
};


3、listen

int listen(int sockfd,int backlog);
功能:监听,将主动套接字变为被动套接字
参数:
 sockfd:套接字
 backlog:同时响应客户端请求链接的最大个数,不能写0.
  不同平台可同时链接的数不同,一般写6-8(队列1:保存正在连接)
(队列2,连接上的客户端)
   返回值:成功 0   失败-1,更新errno 

4、accept

int accept(int sockfd,struct sockaddr* addr,socklen_t* addrlen);
accept(sockfd,NULL,NULL);
阻塞函数,阻塞等待客户端的连接请求,如果有客户端连接,
则accept()函数返回,返回一个用于通信的套接字文件;
参数:
   Sockfd :套接字
   addr: 链接客户端的ip和端口号
      如果不需要关心具体是哪一个客户端,那么可以填NULL;
   addrlen:结构体的大小
     如果不需要关心具体是哪一个客户端,那么可以填NULL;
  返回值: 
     成功:文件描述符;//用于通信
失败:-1,更新errno
 //监听套接字,将主动套接字转为被动套接字
    if (listen(sockfd, 5) < 0)
    {
        perror("listern error.");
        return -1;
    }
    //4.阻塞等待客户端链接,链接成功返回一个用于通信的文件描述符
    acceptfd = accept(sockfd, NULL, NULL);
    if (acceptfd < 0)
    {
        perror("accept error.");
        return -1;
    }
    printf("acceptfd=%d
", acceptfd);

5、recv

ssize_t recv(int sockfd,void*buf, size_t len,int flags);
功能: 接收数据 
参数: 
    sockfd: acceptfd ;
    buf  存放位置
    len  大小
    flags  一般填0,相当于read()函数
    MSG_DONTWAIT  非阻塞
返回值: 
	<0  失败出错  更新errno
	==0  表示客户端退出
	>0   成功接收的字节个数
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
    //创建套接字
    int sockfd;
    int acceptfd;
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("socket error.");
        return -1;
    }
    printf("sockfd=%d
", sockfd);
    //填充ipv4的通信结构体
    struct sockaddr_in serveraddr;
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(8888);
    serveraddr.sin_addr.s_addr = inet_addr("192.168.50.83");

    //绑定套接字
    if (bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0)
    {
        perror("bind error.");
        return -1;
    }
    printf("bind ok.
");
    //3.监听套接字,将主动套接字转为被动套接字
    if (listen(sockfd, 5) < 0)
    {
        perror("listern error.");
        return -1;
    }
    //4.阻塞等待客户端链接,链接成功返回一个用于通信的文件描述符
    acceptfd = accept(sockfd, NULL, NULL);
    if (acceptfd < 0)
    {
        perror("accept error.");
        return -1;
    }
    printf("acceptfd=%d
", acceptfd);
    //5.循环接收消息
    char buf[128];
    int recvbyte;
    while (1)
    {
        recvbyte = recv(acceptfd, buf, sizeof(buf), 0);
        if (recvbyte < 0)
        {
            perror("recv error.");
            return -1;
        }
        else if (recvbyte == 0)
        {
            printf("client exit.
");
            break;
        }
        else
        {
            buf[recvbyte] = '';
            printf("buf:%s
", buf);
        }
    }
    close(acceptfd);
    close(sockfd);
    return 0;
}

6、send

ssize_t send(int sockfd,constvoid*buf, size_t len,int flags);
功能:发送数据
参数:
    sockfd:socket函数的返回值
    buf:发送内容存放的地址
    len:发送内存的长度
    flags:如果填0,相当于write();
返回值: 
<0  失败出错  更新errno
>0   成功发送的字节个数

7、connet

int connect(int sockfd,const struct sockaddr*addr,socklen_t addrlen);
功能:用于连接服务器;
参数:
     sockfd:socket函数的返回值
     addr:填充的结构体是服务器端的;
     addrlen:结构体的大小
返回值 
-1 失败,更新errno
      正确 0
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
    //创建套接字
    int sockfd;
    int acceptfd;
    char buf[128];
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("socket error.");
        return -1;
    }
    printf("sockfd=%d
", sockfd);
    //填充ipv4的通信结构体
    struct sockaddr_in serveraddr;
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(8888);
    serveraddr.sin_addr.s_addr = inet_addr("192.168.50.83");
   
    //连接到服务器端
    if (connect(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0)
    {
        perror("connect err.");
        return -1;
    }
    printf("connect success.
");
    ssize_t printbyte;
    while (1)
    {
        memset(buf, 0, sizeof(buf));                         
        printf("send:");
        scanf("%s", buf);
      
        if ((printbyte = send(sockfd, buf, sizeof(buf), 0)) <0)
        {
            perror("send err.");
        }
    }

    return 0;
}

实现双工通信

server.c

#include <stdio.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
 #include <sys/wait.h>
 #include <string.h>

int main(int argc, char const *argv[])
{
    if (argc != 2)
    {
        printf("please input %s <port>
", argv[0]);
        return -1;
    }
    int sockfd, acceptfd;
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("socket err.");
        return -1;
    }
    struct sockaddr_in serveraddr, caddr;
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(atoi(argv[1]));
    // serveraddr.sin_addr.s_addr=inet_addr(argv[1]);

    //自动获取ip
    //serveraddr.sin_addr.s_addr=htonl(INADDR_ANY);
    // serveraddr.sin_addr.s_addr=INADDR_ANY; //0.0.0.0
    serveraddr.sin_addr.s_addr = inet_addr("0.0.0.0");

    socklen_t len = sizeof(caddr);

    if (bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0)
    {
        perror("bind err.");
        return -1;
    }

    if (listen(sockfd, 5) < 0)
    {
        perror("listen err.");
        return -1;
    }
    while (1)
    {
        acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &len);
        if (acceptfd < 0)
        {
            perror("accept err.");
            return -1;
        }
        printf("client:ip=%s  port=%d
",
               inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));

        char buf[128];
        pid_t pid = fork();
        if (pid < 0)
        {
            perror("fork err.");
            return -1;
        }
        else if (pid == 0) //发送
        {
            int sendbyte;
            while (1)
            {
                fgets(buf, sizeof(buf), stdin);
                if (buf[strlen(buf) - 1] == '
')
                    buf[strlen(buf) - 1] = '';
                sendbyte = send(acceptfd, buf, sizeof(buf), 0);

                if (sendbyte < 0)
                {
                    perror("send error.");
                    return -1;
                }
            }
        }
        else
        {

            int recvbyte;
            while (1)
            {
                recvbyte = recv(acceptfd, buf, sizeof(buf), 0);
                if (recvbyte < 0)
                {
                    perror("recv err.");
                    return -1;
                }
                else if (recvbyte == 0)
                {
                    printf("client exit.
");
                    kill(pid,SIGKILL);
                    wait(NULL);
                    break;
                }
                else
                {
                    printf("buf:%s
", buf);
                }
            }
            close(acceptfd);
        }
    }
    close(sockfd);
    return 0;
}

celient.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
 #include <sys/wait.h>


int main(int argc, char const *argv[])
{
    if (argc != 3)
    {
        printf("please input %s <ip> <port>
", argv[0]);
        return -1;
    }
    //1.创建套接子
    int sockfd;
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("socket error.");
        return -1;
    }
    printf("sockfd=%d
", sockfd);
    //填充ipv4的通信结构体  服务器的ip和端口
    struct sockaddr_in serveraddr;
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(atoi(argv[2]));
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);

    //2.请求链接服务器
    if (connect(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0)
    {
        perror("connect error.");
        return -1;
    }

    //5.循环发送消息  通信
    char buf[128];
    pid_t pid = fork();
    if (pid < 0)
    {
        perror("fork err.");
        return -1;
    }
    else if (pid == 0)
    {
        int recvbyte;
        while (1)
        {
            recvbyte = recv(sockfd, buf, sizeof(buf), 0);
            if (recvbyte < 0)
            {
                perror("recv err.");
                return -1;
            }
            printf("buf:%s
", buf);
        }
    }
    else
    {
        int sendbyte;
        while (1)
        {
            fgets(buf, sizeof(buf), stdin);
            if (buf[strlen(buf) - 1] == '
')
                buf[strlen(buf) - 1] = '';
            sendbyte = send(sockfd, buf, sizeof(buf), 0);
            if (sendbyte < 0)
            {
                perror("send error.");
                return -1;
            }
            if(strncmp(buf,"quit",4)==0)
              {
                  kill(pid,SIGKILL);
                   wait(NULL);
                  exit(-1);
              }
        }
    }
    close(sockfd);
    return 0;
}

优化代码

1.去掉fget获取多余的‘
’
if(buf[strlen(buf)-1]=='
')
         buf[strlen(buf)-1]='';
2.端口和ip地址通过命令行传参到代码中。
3.设置客户端退出,服务器结束循环接收。
    通过recv返回值为0判断客户端是否退出。
4.设置来电显示功能,获取到请求链接服务器的客户端的ip和端口。
5.设置服务器端自动获取自己的ip地址。
   INADDR_ANY  "0.0.0.0"
6.实现循环服务器,服务器不退出,当链接服务器的客户端退出,服务器等到下一个客户端链接。
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。