网络编程-TCP、UDP编程

本文详细介绍了TCP编程中的服务器端套接字操作,包括socket创建、bind、listen、accept和数据接收,以及UDP编程的基本流程,如数据报套接字的创建、指定目标地址发送和接收。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

TCP编程

流程

客户端: 发送请求

服务器端: 相应请求

服务器端(server):

1) socket(),创建套接字文件,创建出用于连接的套接字文件

2) bind(), 绑定,把socket()函数返回的文件描述符和IP、端口号进行绑定;

3) listen(), 监听,将socket()返回的文件描述符,由主动套接字变为被动套接字;

4) accept(), 阻塞函数,阻塞等待客户端的连接请求, 返回一个用于通信的套接字文件;

5) recv(), 接收客户端发来的数据;(read)

//6) send(), 发送数据;(write)

7) close(), 关闭文件描述符; 至少要关闭: 连接、通信

客户端(client):

1. socket(),创建套接字文件,既用于连接,也用于通信;

填充结构体: 填充服务器的ip和端口 , 用于connect连接

2) connect(); 用于发起连接请求,阻塞等待连接服务器;

3) send(), 发送数据;

//4) recv(), 接收数据;

5)close(), 关闭文件描述符;

函数接口:

服务器 server:

1. socket创建套接字
int socket(int domain, int type, int protocol);
头文件: #include <sys/types.h>   #include <sys/socket.h>
功能:创建套接字
参数:
   domain:协议族
     AF_UNIX, AF_LOCAL  本地通信
     AF_INET            ipv4
     AF_INET6            ipv6
   type:套接字类型
     SOCK_STREAM:流式套接字
     SOCK_DGRAM:数据报套接字
   protocol:协议 - 填0 自动匹配底层 ,根据type
      系统默认自动帮助匹配对应协议
       传输层:IPPROTO_TCP、IPPROTO_UDP、IPPROTO_ICMP
       网络层:htons(ETH_P_IP|ETH_P_ARP|ETH_P_ALL)
 返回值:
    成功 文件描述符 0 -> 标准输入  1->标准输出  2->标准出错 
                  3->socket
    失败 -1,更新errno
    //1.创建套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("socket err");
    }
    printf("socket: %d\n", sockfd);
2. bind绑定套接字
头文件: #include<sys/types.h>  #include<sys/socket.h> 
         #include<netinet/in.h>  #include<netinet/ip.h>

功能:绑定协议, IP以及port

声明: int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

参数:
    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;  ----协议族
    in_port_t      sin_port;   ----端口
    struct in_addr sin_addr;     ----ip结构体
};
struct in_addr {
    uint32_t       s_addr;     --ip地址
};
    //填充结构体 服务器
    struct sockaddr_in saddr, caddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(8889);
    saddr.sin_addr.s_addr = inet_addr("192.168.51.192");
    int len = sizeof(caddr);

    //bind 绑定ip
    if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
    {
        perror("bind err");
        return -1;
    }
3. listen监听
int listen(int sockfd, int backlog);
功能:监听,将主动套接字变为被动套接字
参数:
 sockfd:套接字
 backlog:同一时间可以响应客户端请求链接的最大个数,不能写0.
  不同平台可同时链接的数不同,一般写6-8个

返回值:成功 0   失败-1,更新errno  
 if (listen(sockfd, 5) < 0)
    {
        perror("listen err");
        return -1;
    }
    printf("listen success\n");
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新errno 
int acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &len);
    if (acceptfd < 0)
    {
        perror("accept err");
        return -1;
    }
    printf("accept succes\n");

在成功接受连接后,accept() 函数会将客户端的地址信息填充到 caddr 结构体中,以供后续使用

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   成功接收的字节个数
 int recvbyte = recv(acceptfd, buf, sizeof(buf), 0);
        if (recvbyte < 0)
        {
            perror("recv err");
            return -1;
        }
        else if (recvbyte == 0)
        {
            printf("ip: %s  port : %d  client is exit\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));
            break;
        }
        else
        {
            printf("%s\n", buf);
        }

总结

服务器:

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
    //1.创建套接字 >> 返回一个建立连接的文件描述符
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(sockfd < 0)
    {
        perror("socket is err:");
    }
    printf("sockfd : %d\n",sockfd); //3

    //2. 填充结构体
    struct sockaddr_in saddr,caddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(argv[1]);
    saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
    int len = sizeof(caddr);

    //bind绑定ip和端口
    if(bind(sockfd,(struct sockaddr *)&saddr,sizeof(saddr))<0)
    {
        perror("bind is err:");
        return -1;
    }
    //3. listen 监听
    if(listen(sockfd,5)<0)
    {
        perror("listen is err:");
        return -1;
    }
    printf("listen is success\n");

    //4. 阻塞 等待客户端连接 -> 返回一个同于通信的文件描述符
    int acceptfd = accept(sockfd,(struct sockaddr *)&caddr,&len);
    if(acceptfd < 0)
    {
        perror("accept is err:");
        return -1;
    }

    char buf[128] = "";
    while(1)
    {  //recv接收客户端发送的内容
        int recvbyte = recv(acceptfd,buf,sizeof(buf),0);
        if(recvbyte < 0) //函数出错
            {
                perror("recv is err:");
                return -1;
            }
        else if(recvbyte == 0) //表示客户端退出
            {
                printf("ip: %s  port : %d  client is exit\n",\
                       inet_ntoa(caddr.sin_addr),ntohs(caddr.sin_port));
                break;
            }
        else  //打印客户端的内容
            {
                printf("%s\n",buf);
            }
    }
    close(acceptfd);
    close(sockfd);
    return 0;
}

客户端:

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char const *argv[])
{
    //1.创建socket套接字 - 仅仅具有连接的功能
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(sockfd < 0)
    {
        perror("socket is err:");
        return -1;
    }
    //2.填充结构体
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(atoi(argv[1]));
    saddr.sin_addr.s_addr = inet_addr(argv[2]);
    //连接成功之后, sockfd同时具有通信的功能
    if(connect(sockfd,(struct sockaddr *)&saddr,sizeof(saddr)) < 0)
    {
        perror("connect is err:");
        return -1;
    }

    char buf[128] = "";
    while(1)
    {
        //终端输入, 内容存于 buf 内
        fgets(buf,sizeof(buf),stdin);
          if(buf[strlen(buf)-1] == '\n')
             buf[strlen(buf)-1] = '\0';
        //发送buf的内容
        send(sockfd,buf,sizeof(buf),0);
    }
    //关闭文件描述符
    close(sockfd);
    return 0;
}

优化代码

1.客户端发送去掉fgets获取的多余的'\n'.

fgets(实际读到的内容小于等于指定个数-1,自动读到的内容后添加’\0’,会将’\n’也读入)

if(buf[strlen(buf)-1] == '\n')//去掉fgets获取的'\n'

buf[strlen(buf)-1] ='\0';

2.客户端端口和ip地址通过命令行传参到代码中;(如果参数不一致,应该提示并退出)

3.设置客户端退出,服务器结束循环接收。

通过recv返回值为0判断客户端是否退出

4.设置来电显示功能,获取到请求链接服务器的客户端的ip和端口。

int acceptfd = accept(sockfd,(struct sockaddr *)&caddr,&len);

打印时记得使用 inet_ntoa()和ntohs()转换为主机字节序

5.设置服务器端自动获取自己的ip地址。

inet_addr("0.0.0.0")

6.实现循环服务器,服务器不退出,当链接服务器的客户端退出,服务器等到下一个客户端链接。

accept处使用while

7. 当客户端输入quit的时候,客户端和服务器服务器都要退出

strncmp

优化版本
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>

int main()
{
    // 创建套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("socket error");
        return -1;
    }

    // 设置套接字选项以允许地址重用
    int reuse = 1;
    if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(int)) == -1)
    {
        perror("setsockopt error");
        close(sockfd);
        return -1;
    }

    // 配置服务器地址结构体
    struct sockaddr_in saddr, caddr;
    memset(&saddr, 0, sizeof(saddr));
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(8889);
    saddr.sin_addr.s_addr = INADDR_ANY;

    // 绑定套接字到指定地址
    if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) == -1)
    {
        perror("bind error");
        close(sockfd);
        return -1;
    }

    // 监听连接请求
    if (listen(sockfd, 5) == -1)
    {
        perror("listen error");
        close(sockfd);
        return -1;
    }

    printf("Server is listening for connections...\n");

    while (1)
    {
        socklen_t len = sizeof(caddr);
        // 接受客户端连接请求
        int acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &len);
        if (acceptfd == -1)
        {
            perror("accept error");
            close(sockfd);
            return -1;
        }

        // 打印客户端连接信息
        printf("Accepted connection from %s:%d\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));

        char buf[128];
        while (1)
        {
            // 接收客户端发送的数据
            ssize_t recvbyte = recv(acceptfd, buf, sizeof(buf) - 1, 0);
            if (recvbyte == -1)
            {
                perror("recv error");
                close(acceptfd);
                break;
            }
            else if (recvbyte == 0)
            {
                printf("Connection closed by client\n");
                close(acceptfd);
                break;
            }
            else
            {
                buf[recvbyte] = '\0';
                printf("Received: %s", buf);
            }
        }
        close(acceptfd);  // Close the client socket after processing
    }

    close(sockfd);
    return 0;
}

UDP编程 介绍

通信流程 --- 无连接(connect accept)的过程

UDP 无法判断客户端是否退出:

使用心跳包: 使用客户端, 定时给服务器发送内容

udp流程:(类似发短信)

server:

创建数据报套接字(socket(,SOCK_DGRAM,))----->有手机

绑定网络信息(bind())-----------> 绑定IP和port(发短信知道发给谁)

接收信息(recvfrom())------------>接收信息,同时可以获取到发送者的IP和port

关闭套接字(close())-------------->接收完毕

client:

创建数据报套接字(socket())----------------------->有手机

指定服务器的网络信息------------------------------>有对方号码

发送信息(sendto())---------------------------->发送短信,根据填充的结构体信息

关闭套接字(close())--------------------------->发送完

函数接口

recvfrom
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
					struct sockaddr *src_addr, socklen_t *addrlen);
功能:接收数据
参数:
	sockfd:套接字描述符
	buf:接收缓存区的首地址
	len:接收缓存区的大小
	flags:0  接收数据 并 阻塞
                 MSG_DONTWAIT: 设置非阻塞
	src_addr: 发送端的网络信息结构体的指针(对方的 caddr)
	addrlen:发送端的网络信息结构体的大小的指针(对方的 caddr)
返回值:
	成功接收的字节个数
       接收到的数据为0 : 0
	失败:-1
 int recvbyte = recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&caddr, &len);
        if (recvbyte < 0)
        {
            perror("recvfrom is err ");
            return -1;
        }
sendto
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                  const struct sockaddr *dest_addr, socklen_t addrlen);
功能:发送数据
参数:
	sockfd:套接字描述符
	buf:发送缓存区的首地址
	len:发送缓存区的大小
	flags:0  发送消息并阻塞
	src_addr:接收端的网络信息结构体的指针
	addrlen:接收端的网络信息结构体的大小
返回值: 
	成功发送的字节个数
	失败:-1
sendto(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&saddr, sizeof(saddr));

注意:

1、对于TCP是先运行服务器,客户端才能运行。

2、 对于UDP来说,服务器和客户端运行顺序没有先后,因为是无连接,所以服务器和客户端谁先开始,没有关系,

3、 UDP一个服务器可以同时连接多个客户端。想知道是哪个客户端登录,可以在服务器代码里面打印IP和端口号。

以下内容面试可能会问: 感兴趣可以自己测试一下

4、UDP,客户端当使用send的时候,上面需要加connect,,这个connect不是代表连接的作用,而是指定客户端即将要发送给谁数据。这样就不需要使用sendto而用send就可以。

5、在TCP里面,也可以使用recvfrom和sendto,使用的时候将后面的两个参数都写为NULL就OK。

send,sendto 区别

send(),recv()用于TCP,sendto()及recvfrom()用于UDP
但是send(),recv()也可以用于UDP,sendto()及recvfrom()也可以用于TCP

sendto可以在参数中指定发送的目标地址 , send需要socket已建立连接, sendto 可用于无连接的 socket 对于send的有连接socket,两者一样,sendto最后两个参数没用.

总结

服务器:

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <arpa/inet.h>
#include <stdlib.h>

int main(int argc, char const *argv[])
{
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        perror("socket is err");
        return -1;
    }
    struct sockaddr_in saddr, caddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(atoi(argv[1]));
    saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
    int len = sizeof(caddr);
    if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
    {
        perror("bind is err ");
        return -1;
    }

    char buf[128] = "";
    while (1)
    {
        int recvbyte = recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&caddr, &len);
        if (recvbyte < 0)
        {
            perror("recvfrom is err ");
            return -1;
        }
        else
        {
            printf("ip: %s  port:%d  %s",saddr.sin_addr,saddr.sin_port,buf);
        }
    }

    close(sockfd);
    return 0;
}

客户端:

#include <stdio.h>
#include <unistd.h>
#include <string.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 <stdlib.h>

int main(int argc, char const *argv[])
{
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        perror("socket is err ");
        return -1;
    }
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(atoi(argv[2]));
    saddr.sin_addr.s_addr = inet_addr(argv[1]);

    char buf[128];
    while (1)
    {
        fgets(buf, sizeof(buf), stdin);
        if (buf[strlen(buf) - 1] == '\n')
            buf[strlen(buf) - 1] = '0';
        sendto(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&saddr, sizeof(saddr));
    }
    close(sockfd);

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值