UDP服务器的实现

先介绍一个重要的函数: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

运行结果:


没有更多推荐了,返回首页