UDP服务器的实现

news/2024/5/18 12:07:58 标签: 套接字, socket, UDP, sockaddr
先介绍一个重要的函数:socket(),socket()是用于创建一个套接字的。
函数原型:

参数: domain确定了通信的特性也包括地址格式,比如AF_INET就是IPV4协议,AF_INET6就是IPV6协议,AF_UNIX是UNIX域。
             type是套接字的类型,进一步的确定了通信的特性,套接字类型以SOCK_开头,常用的如SOCK_DGRAM就是UDP协议,SOCK_STREAM就是流式套接字,是TCP协议。
            最后一个参数protocol通常设置为0.
返回值类型: 是一个文件描述符。
         稍微讲解一下为什么返回的是文件描述符:在Linux下一切皆文件, 我们想要往一个文件里边写数据或者读数据,首先得先将这个文件打开,文件打开函数会返回一个文件描述符给我们,然后,我们就可以通过文件描述符对该文件进行操作了,也就是说一个文件描述符对应一个文件。此外 ,以三个文件默认打开的文件描述符,0-----标准输入,1-----标准输出,2------标准出错。标准输入对应的设备就是键盘,当我们通过键盘输入时,会将输入的内容存入到 文件描述符 0 对应的这个文件,也就是说,这个文件就代表着键盘。同样的,屏幕也是一个文件。键盘和屏幕都是外设,这些外设对于Linux系统来说也都是文件,我们将数据写传入到网络上去其实就是将数据写入网卡这个外设中,从网络上获取数据就是从网卡中读取数据。键盘和屏幕可以看做是一个文件,同样的,也可以将网卡看做是一个文件,创建套接字的过程就好像是为进程打开了网卡这个文件,所以,返回的也就是一个文件描述符了。

sockaddr结构

        IPv4和IPv6的地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in 结构表示,包括16位地址类型,16位端口号和32位IP地址。
        IPv4、IPv6地址类型分别定义为常数AF_INET、AF_INET6. 这样,只要取得某种sockaddr结构体的 首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内 容.
        socket API可以都用struct sockaddr *类型表示, 在使用的时候需要强制转化成sockaddr_in; 这样的 好处是程序的通用性, 可以接收IPv4, IPv6, 以及UNIX Domain Socket各种类型的sockaddr结构体 指针做为参数。这个struct sockaddr * 的作用类似于void*,那么为什么不使用void* 呢?因为在struct sockaddr* 开始使用的时候根本就还没有void* ,后来才有的void* ,但是再把它改成void* 又太麻烦了,所以,就一直采用这种写法了。
虽然socket api的接口是sockaddr, 但是我们真正在基于IPv4编程时, 使⽤用的数据结构是sockaddr_in; 这个结构 里主要有三部分信息: 地址类型, 端口号, IP地址。

in_addr用来表示一个IPv4的IP地址. 其实就是一个32位的整数;

UDP服务器的具体实现:
UDP服务器端
1、创建套接字
int  socket(int domain,int type, int  protocol);
2、 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket,  const struct sockaddr *address,   socklen_t address_len);
3、 使用sendto和recvfrom来进行数据读写.
   使用sendto函数和recvfrom函数需要包含如下两个头文件
   
 #include <sys/types.h>
 #include <sys/socket.h>
sendto()函数是用来传输消息到另一个套接字的。
    ssize_t sendto(int sockfd, const void *buf,   size_t len, int flags,  const struct sockaddr *dest_addr,   socklen_t addrlen);
recvfrom函数是用来接收套接字上的数据
    ssize_t recvfrom(int sockfd,  void *buf, size_t len,  int flags, struct sockaddr *src_addr,  socklen_t *addrlen);

UDP客户端
创建一个套接字后,可以不绑定,可以直接向UDP服务器发送和接收消息。为什么服务器端需要绑定而客户端就不需要绑定呢?
      原因在于服务器端需要给很多用户提供服务,为了方便用户能够找到它,给服务器绑定一个socket(IP地址+端口号),客户就可以随时找到服务器并请求服务了,如果不绑定的话,就是有系统自动默认指定socket的参数了。客户端可以不绑定是因为,客户端不需要给其他人提供服务,客户端在请求服务时,会把自己的socket传给服务器,那么服务器就可以通过传入的参数找到客户端并为之提供服务了,所以客户端 就没有担心别人找不到他这种苦恼。

server服务器部分
#include <stdio.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>

int main(int argc ,char* argv[])
{
	if(argc < 3)
	{
		printf("./server [addr:] [port:]\n");
		return 4;
	}
        //创建一个套接字
	int sock = socket(AF_INET,SOCK_DGRAM,0);//SOCK_DGRAM代表UDP
	if(sock < 0)
	{
		perror("socket");
		return 1;
	}

	struct sockaddr_in local;
	local.sin_family = AF_INET;
	local.sin_port = htons(atoi(argv[2]));
	local.sin_addr.s_addr = inet_addr(argv[1]);
	//绑定
	if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
	{
		perror("bind");
		return 2;
	}

	char buf[1024];
	struct sockaddr_in client;
	socklen_t len = sizeof(client);
	while(1)
	{
		ssize_t s = recvfrom(sock,buf,sizeof(buf)-1,0,(struct sockaddr*)&client,&len);
		if(s<0)
		{
			perror("recvfrom");
			return 3;
		}
		else if(s == 0)
		{
			printf("client closed\n");
		}
		else
		{
			buf[s] = 0;
			printf("[%s : %d]:%s\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port),buf);
			sendto(sock,buf,strlen(buf),0,(struct sockaddr*)&client,len);
			
			if(strcmp(buf,"quit") == 0)
				break;
		}

	}

	return 0;
}

client客户端部分

#include <stdio.h>
#include <unistd.h>
#include <arpa/inet.h>

int main(int argc ,char* argv[])
{
	if(argc < 3)
	{
		printf("./client [addr:][port:]\n");
		return 4;
	}
	int sock = socket(AF_INET,SOCK_DGRAM,0);
	if(sock < 0)
	{
		perror("socket");
		return 1;
	}

	struct sockaddr_in server;
	server.sin_family = AF_INET;
	server.sin_port = htons(atoi(argv[2]));
	server.sin_addr.s_addr = inet_addr(argv[1]);
	
	char buf[1024];
	struct sockaddr_in peer;
	socklen_t len = sizeof(peer);
	while(1)
	{
		printf("#please enter#  ");
		fflush(stdout);
		ssize_t s = read(0,buf,sizeof(buf)-1);
		if(s < 0)
		{
			perror("read");
			return 2;
		}
		buf[s-1] = 0;
		sendto(sock,buf,sizeof(buf)-1,0,(struct sockaddr*)&server,sizeof(server));
		s = recvfrom(sock,buf,sizeof(buf)-1,0,(struct sockaddr*)&peer,&len);
		if(s<0)
		{
			perror("recvfrom");
			return 3;
		}
		else if(s == 0)
		{
			printf("server closed\n");
		}
		else
		{
			buf[s] = 0;
			printf("#server echo#  %s\n",buf);
			if(strcmp(buf,"quit") == 0)
				break;
		}
	}

	return 0;
}

Makefile文件

.PHONY:all
all:client server

client:client.c
	gcc -o $@ $^
server:server.c
	gcc -o $@ $^
.PHONY:clean
clean:
	rm -f client server

运行结果:



http://www.niftyadmin.cn/n/1014653.html

相关文章

IDE相关(三)Pycharm安装教程 MySQL MSI安装与完全卸载

1.手把手教你如何安装Pycharm——靠谱的Pycharm安装详细教程 激活时遇到问题请参考一楼评论解决。 2.MySQL 安装&#xff08;博客&#xff09; 3.MySQL及workbench安装&#xff08;视频&#xff09; 4.安装mysql错误&#xff0c;想重新安装&#xff0c;卸载不干净&#xff0…

从卓越工程的角度看微软中国开发团队的成长 (二)

上篇我们讨论卓越工程系统中的人才因素&#xff0c;本篇探讨第二个重要因素——流程。 简捷有效的流程 人是有思维的、有创造力的&#xff0c;可是在做一些具体事务时却容易出一些低级的错误。这时流程会帮助减少这样的错误来保证产品的质量。流程如果太繁琐会降低效率&#xf…

二级空间配置器的原理剖析和简单实现

首先来讲一下频繁地向系统申请小内存块的缺点 用户代码与操作都是在用户态&#xff0c;而操作系统是属于内核态的&#xff0c;用户在向系统申请空间的时候是通过操作系统来申请的&#xff0c;所以&#xff0c;每一次空间申请就会进行用户态与内核态之间的切换&#xff0c;会大大…

助你写英文简历(2)--很不错的东东(特别推荐)

教育背景&#xff08;Educational background&#xff09; • 即正规学校教育/或培训&#xff08;A history of a person’s formal schooling and/or training.&#xff09;包括&#xff1a; • 学历&#xff08;educational history &#xff09;、 • 教育程度&#xff08;e…

IDE相关(四)Virtual Studio2017 + OpenCV3.4.3 + C++

本机环境&#xff1a;win7 64位 OpenCV3.4.3 Visual Studio 2017 一、下载OpenCV3.4.3 OpenCV官方下载地址&#xff1a;https://opencv.org/releases.html 选择Windows平台安装包 下载完成后&#xff0c;双击进行解压 解压即是安装OpenCV库的过程&#xff0c;选择安装路径…

《敏捷软件开发》读书笔记 (1)

测试 编写单元测试是一种验证行为&#xff0c;更是一种设计行为 测试先行&#xff0c;迫使我们把程序设计为可测试的 测试是无价的文档 在编写代码前先编写测试改善了设计 为了使验收测试无须通过用户界面就能获得对业务规则的访问&#xff0c;我们需要解除业务规则和用户界面…

有病

我记得在《手机》立&#xff0c;严守一主持的一期节目的名字叫有病&#xff0c;我时常觉得这个社会就是病态的&#xff0c;总是有一种说不出的感觉&#xff0c;就好比突然之间停下了手头的工作然后仔细一想发现你正在做的事情居然毫无意义&#xff0c;本来以为会过得很开心的时…

C语言控制台小游戏之下落的字符

用C语言写个小游戏放松下。。。 游戏效果&#xff1a; 回车键——开始0——结束1——暂停规则&#xff1a;键盘输入滑落的字母&#xff08;不必区分大小写&#xff09;&#xff0c;每正确一个加10分&#xff0c;没来得及输入减10分&#xff0c;输入错误不加也不减。-50分以下…