UDP编程

通信流程

UDP编程_第1张图片

UDP编程 

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

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

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

UDP编程_第2张图片

udp流程:(类似发短信)

server:

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

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

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

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

client:

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

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

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

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

函数接口

1、recvfrom接受数据

#include 
#include 
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
					struct sockaddr *src_addr, socklen_t *addrlen);
功能:接收数据

参数:
	sockfd:套接字描述符
	buf:接收缓存区的首地址
	len:接收缓存区的大小
	flags:0
	src_addr:发送端的网络信息结构体的指针
	addrlen:发送端的网络信息结构体的大小的指针
  
返回值:
	成功接收的字节个数
	失败:-1
	0:客户端退出


2、sendto 发送数据

#include 
#include 
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

注意:

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

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

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

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

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

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

/*服务器创建代码 */
#include 
#include  /* See NOTES */
#include 
#include 
#include  /* superset of previous */
#include 
#include 
#include 
#include 

int main(int argc, char const *argv[])
{
    if (argc < 2)
    {
        printf("plase input \n");
        return -1;
    }
    //1.创建套接字,用于链接
    int sockfd;
    sockfd = socket(AF_INET,SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        perror("socket err");
        return -1;
    }
    printf("sockfd:%d\n", sockfd);
    //2.绑定 ip+port 填充结构体
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;            
    saddr.sin_port = htons(atoi(argv[1])); 
    saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
    socklen_t len = sizeof(saddr); //结构体大小
    //bind绑定ip和端口
    if (bind(sockfd, (struct sockaddr *)&saddr, len) < 0)
    {
        perror("bind err");
        return -1;
    }
    printf("bind success\n");
    char buf[128] = {0};
    while (1)
    {
        //接收信息
        if (recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&saddr, &len) < 0)
        {
            perror("recvfrom err");
            return -1;
        }
        printf("client ip:%s ,port:%d buf:%s\n", inet_ntoa(saddr.sin_addr), ntohs(saddr.sin_port),buf);
        //发送信息
        printf("server:");
        fgets(buf, sizeof(buf), stdin); //从终端获取内容存放到数组中
        if (strncmp(buf, "quit", 4) == 0) //输入quit退出客户端
        {
            break;
        }
        if (buf[strlen(buf)] == '\0')
        {
            buf[strlen(buf) - 1] = '\0';
        }
        sendto(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&saddr, len);
    }
    close(sockfd);
    return 0;
}

/*客户端创建代码 */
#include 
#include  /* See NOTES */
#include 
#include 
#include  /* superset of previous */
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main(int argc, char const *argv[])
{
    if(argc<3)
    {
        printf("plase input ")
    }
    //1.创建套接字,用于链接
    int sockfd;
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        perror("socket err");
        return -1;
    }
    printf("sockfd:%d\n", sockfd);
    //2.填充结构体
    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]);
    socklen_t len = sizeof(saddr); //结构体大小
    char buf[128] = {0};
    int ret;
    while (1)
    {
        //发送信息
        printf("client:");
        fgets(buf, sizeof(buf), stdin);   //从终端获取内容存放到数组中
        if (strncmp(buf, "quit", 4) == 0) //输入quit退出客户端
        {
            break;
        }
        if (buf[strlen(buf)] == '\0')
        {
            buf[strlen(buf) - 1] = '\0';
        }
        sendto(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&saddr, len);
        //接受信息
        if (recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&saddr, &len) < 0)
        {
            perror("recvfrom err");
            return -1;
        }
        printf("server buf:%s\n", buf);
    }
    close(sockfd);
    return 0;
}

练习:实现如客户端发送"hello"给服务器端,服务器接着给客户端回,"recv:hello!!!!!"。 

作业:熟练记忆TCP\UDP客户端服务器,标准为可以默写出来。

项目-网络聊天室

项目要求

利用UDP协议,实现一套聊天室软件。服务器端记录客户端的地址,客户端发送消息后,服务器群发给各个客户端软件,服务也可以自己发送通知给所有客户端。

问题思考

  • 客户端会不会知道其它客户端地址?

UDP客户端不会直接互连,所以不会获知其它客户端地址,所有客户端地址存储在服务器端。

  • 有几种消息类型?
  • 登录:服务器存储新的客户端的地址。把某个客户端登录的消息发给其它客户端。
  • 聊天:服务器只需要把某个客户端的聊天消息转发给所有其它客户端。
  • 退出:服务器删除退出客户端的地址,并把退出消息发送给其它客户端。
  • 服务器如何存储客户端的地址?

数据结构可以选择线性数据结构

链表节点结构体:
struct node{
	struct sockaddr_in addr;//data   memcmp
	struct node *next;
};

消息对应的结构体(同一个协议)
typedef struct msg_t
{
    int type;//'L' C  Q    enum un{login,chat,quit};
    char name[32];//用户名
    char text[128];//消息正文
}MSG_t;

int memcmp(void *s1,void *s2,int size)
功能:比较两个空间内的值是否完全相同

  • 客户端如何同时处理发送和接收?

客户端不f仅需要读取服务器消息,而且需要发送消息。读取需要调用recvfrom,发送需要先调用fgets,两个都是阻塞函数。所以必须使用多任务来同时处理,可以使用多进程或者多线程来处理。

程序流程图

服务器端

UDP编程_第3张图片

客户端

UDP编程_第4张图片

你可能感兴趣的:(网络编程,开发语言,udp,IO,linux,网络协议,网络,c语言)