Linux系统编程(14)UDP全双工通信和TCP半双工通信

一、UDP全双工通信

UDP通信基础:
recvfrom函数

  recvfrom 是一个用于接收数据的函数,,但 recvfrom 不仅接收数据,还可以获取发送数据的地址信息。

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
  • sockfd: 指定用于接收数据的套接字描述符。它通常是通过 socket() 函数创建的。

  • buf: 指向接收数据的缓冲区的指针。

  • len: 指定缓冲区的大小,即要接收的数据的最大字节数。

  • flags: 接收数据时的标志位,默认为0

  • src_addr: 指向存储发送方地址信息的 sockaddr 结构体的指针。如果不关心发送方的地址信息,可以传入 NULL

  • addrlen: 指向 src_addr 结构体长度的指针。在函数返回时,它将包含实际的地址长度。

  • 成功时,recvfrom 返回接收到的字节数。
  • 出错时,返回 -1
bind函数

    bind 函数在网络编程中用于将一个套接字与一个特定的地址(IP地址和端口号)绑定。它主要用于服务器端,使得服务器能够监听来自特定地址的连接请求。

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • sockfd: 套接字文件描述符。这个套接字是通过 socket() 函数创建的,表示一个未绑定的套接字。

  • addr: 指向 sockaddr 结构体的指针,包含要绑定到的地址信息。通常使用 sockaddr_in 结构体来表示IPv4地址(对于IPv6使用 sockaddr_in6),然后将其强制转换为 sockaddr* 类型。

  • addrlen: 结构体 sockaddr 的大小,通常使用 sizeof(struct sockaddr_in)sizeof(struct sockaddr_in6)

  • 成功时,返回 0
  • 出错时,返回 -1

struct sockaddr结构体:

   struct sockaddr      通用地址结构 --- ip + 端口 
      {
          u_short sa_family;  地址族
          char sa_data[14];   地址信息
      };

      转换成网络地址结构如下:
    

 struct sockaddr_in    ///网络地址结构
      {
          u_short           sin_family; //地址族
          u_short           sin_port;   //地址端口
          struct in_addr  sin_addr;   //地址IP  //"192.168.1.123"
          char               sin_zero[8]; //占位
      };
其中sin_addr结构体:
      struct in_addr
      {
          in_addr_t s_addr;
      };
基于UDP实现的全双工通信
client.c
//使用多进程实现客户端的的收和发
#include<stdio.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 <string.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main(int argc , char *argv[])
{
    int fd = socket(AF_INET,SOCK_DGRAM,0);
    if(fd < 0)
    {
        perror("socket error");
        return 0;
    }
    char buf[1024];
    struct sockaddr_in seraddr;
    seraddr.sin_family = AF_INET;
    seraddr.sin_port = htons(50000);
    seraddr.sin_addr.s_addr = inet_addr("192.168.85.128");
    struct sockaddr_in srcaddr;
    socklen_t srcaddrlen = sizeof(srcaddr);
    pid_t pid = fork();
    if(pid < 0)
    {
        perror("fork fail");
        return 0;
    }
    if(pid > 0)
    {
        while(1)
        {
            printf("client >");
            bzero(buf,sizeof(buf));
            fgets(buf,sizeof(buf),stdin); 
            sendto(fd,buf,strlen(buf),0,(const struct sockaddr *)&seraddr,sizeof(seraddr));
            if(strncmp(buf,"quit",4) == 0)
            {
                kill(pid,9);
                wait(NULL);
                return 0;
            }
        }
    }
    if(pid == 0)
    {
        while(1)
        {
            recvfrom(fd,buf,sizeof(buf),0,NULL,NULL);
            printf(" %s\n",buf);

        }
    }
    return 0;
}
server.c
//使用多进程实现服务端的收和发
#include<stdio.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 <string.h>
#include <arpa/inet.h>
#include <signal.h>
#include<sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
void handler(int signo)
{
    wait(NULL);
    kill(getpid(),9);
}
int main(int argc , char *argv[])
{
    int fd = socket(AF_INET,SOCK_DGRAM,0);
    if(fd < 0)
    {
        perror("socket error");
        return 0;
    }
    char buf[1024];
    struct sockaddr_in seraddr;
    seraddr.sin_family = AF_INET;
    seraddr.sin_port = htons(50000);
    seraddr.sin_addr.s_addr = inet_addr("192.168.85.128");
    struct sockaddr_in srcaddr;
    socklen_t srcaddrlen = sizeof(srcaddr);
    bind(fd,(const struct sockaddr *)&seraddr,sizeof(seraddr));
        recvfrom(fd,buf,sizeof(buf),0,(struct sockaddr *) & srcaddr,&srcaddrlen);
    if(strncmp(buf,"quit",4) == 0)
    {
        return 0;
    }
    printf("receive :%s",buf);
    printf("receive from : %s\n",inet_ntoa(srcaddr.sin_addr));
    printf("port : %d\n",ntohs(srcaddr.sin_port));
    char returnbuf[2048];
    pid_t pid = fork();
    if(pid < 0)
    {
        perror("fork fail");
        return 0;
    }
    if(pid > 0)
    { 
        while(1)
        {   //signal(SIGCHLD,handler);
            //bzero(buf,sizeof(buf));
            printf("server >");
            fgets(buf,sizeof(buf),stdin);
            sprintf(returnbuf,"%s%s","\nfrom server:",buf);
            sendto(fd,returnbuf,strlen(returnbuf),0,(const struct sockaddr *)&srcaddr,sizeof(srcaddr));
        }
    }
    if(pid == 0)
    {
        while(1)
        {
            recvfrom(fd,buf,sizeof(buf),0,(struct sockaddr *) & srcaddr,&srcaddrlen);
            if(strncmp(buf,"quit",4) == 0)
            {
                break;
            }
            printf("receive :%s",buf);
            printf("receive from : %s\n",inet_ntoa(srcaddr.sin_addr));
            printf("port : %d\n",ntohs(srcaddr.sin_port));

        }
        return 0;
    }
}

二、TCP通信

由于TCP面向连接和可靠性高的特性,因此,在使用TCP协议通信时,需要在客户端和服务端之间先建立连接才能进行数据通信。

TCP通信流程

listen函数

listen 函数用于将一个套接字设置为监听模式,等待客户端的连接请求。

int listen(int sockfd, int backlog);
  • sockfd: 套接字文件描述符。这个套接字必须已经用 socket() 函数创建,并且使用 bind() 函数绑定到了一个特定的地址和端口。

  • backlog: 指定内核为该套接字排队的最大连接数。这表示在 accept() 函数处理之前,内核可以排队等待的最大连接数。如果有更多的连接请求到达,新的连接可能会被拒绝或未处理,直到队列中有空闲位置。

  • 成功时,返回 0
  • 出错时,返回 -1

connect函数  

 int connect(int sockfd, const struct sockaddr *addr,
                   socklen_t addrlen);

 功能:该函数固定有客户端使用,表示从当前主机向目标
            主机发起链接请求。
   参数:sockfd 本地socket创建的套接子id
                    addr 远程目标主机的地址信息。
                 addrlen: 参数2的长度。
   返回值:成功 0
              失败 -1;

accept函数
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

accept 函数用于服务器端网络编程,在套接字处于监听模式时,accept 函数会从监听队列中提取第一个连接请求,并为该连接创建一个新的套接字(专门用来连接)

  • sockfd: 监听套接字的文件描述符。这个套接字是通过 socket() 创建的,并且已经通过 bind() 绑定到了一个地址和端口,通过 listen() 函数进入了监听状态。

  • addr: 指向 sockaddr 结构体的指针,accept 函数将填充客户端的地址信息到该结构体中。如果你不关心客户端的地址,可以将其设置为 NULL

  • addrlen: 一个值-结果参数,它指向一个保存了 addr 结构大小的变量。在函数返回时,这个变量会包含客户端地址结构的实际大小。如果 addrNULL,则 addrlen 也应为 NULL

  • 成功时,返回一个新的文件描述符,这个描述符代表新建立的连接。服务器通过这个新套接字与客户端进行通信。
  • 出错时,返回 -1,并设置 errno 以指示错误类型。

这几个函数之间的关系

listen函数将创建的socket套接字设置为监听模式后,创建一个序列用来存储请求连接的设备,accept函数每次将第一个在队列中的请求进行处理后产生一个用来通信的和客户端建立连接的新的套接字 ,用来负责后续的通信

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值