linux网络编程之UDP编程


在网络传输协议中,TCP协议提供的是一种可靠的,复杂的,面向连接的数据流(SOCK_STREAM)传输服务,它通过三段式握手过程建立连接。TCP有一种“重传确认”机制,即接收端收到数据后要发出一个肯定确认的信号,发送端如果收到接收端肯定确认的信号,就会继续发送其他的数据,如果没有,它就会重新发送。

相对而言,UDP协议则是一种无连接的,不可靠的数据报(SOCK_DGRAM)传输服务。使用UDP套接口不用建立连接,服务端在调用socket()生成一个套接字并调用bind()绑定端口后就可以进行通信(recvfrom函数和sendto函数)了;客户端在用socket()生成一个套接字后就可以向服务端地址发送和接收数据了。

此处需要特别注意:TCP使用的是流套接字(SOCK_STREAM),UDP使用的是数据报套接字(SOCK_DGRAM)

UDP套接字编程范例:

server端代码如下:

/************************************************************************* 
 > File Name: server.c 
 > Author: Maoyi 
 ************************************************************************/
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
  
#define SERVER_PORT 8000 
#define BUFFER_SIZE 1024 
#define FILE_NAME_MAX_SIZE 512 
  
int main() 
{ 
 /* 创建UDP套接口 */
 struct sockaddr_in server_addr; 
 bzero(&server_addr, sizeof(server_addr)); 
 server_addr.sin_family = AF_INET; 
 server_addr.sin_addr.s_addr = htonl(INADDR_ANY); 
 server_addr.sin_port = htons(SERVER_PORT); 
  
 /* 创建socket */
 int server_socket_fd = socket(AF_INET, SOCK_DGRAM, 0); 
 if(server_socket_fd == -1) 
 { 
  perror("Create Socket Failed:"); 
  exit(1); 
 } 
  
 /* 绑定套接口 */
 if(-1 == (bind(server_socket_fd,(struct sockaddr*)&server_addr,sizeof(server_addr)))) 
 { 
  perror("Server Bind Failed:"); 
  exit(1); 
 } 
  
 /* 数据传输 */
 while(1) 
 {  
  /* 定义一个地址,用于捕获客户端地址 */
  struct sockaddr_in client_addr; 
  socklen_t client_addr_length = sizeof(client_addr); 
  
  /* 接收数据 */
  char buffer[BUFFER_SIZE]; 
  bzero(buffer, BUFFER_SIZE); 
  if(recvfrom(server_socket_fd, buffer, BUFFER_SIZE,0,(struct sockaddr*)&client_addr, &client_addr_length) == -1) 
  { 
   perror("Receive Data Failed:"); 
   exit(1); 
  } 
  
  /* 从buffer中拷贝出file_name */
  char file_name[FILE_NAME_MAX_SIZE+1]; 
  bzero(file_name,FILE_NAME_MAX_SIZE+1); 
  strncpy(file_name, buffer, strlen(buffer)>FILE_NAME_MAX_SIZE?FILE_NAME_MAX_SIZE:strlen(buffer)); 
  printf("%s\n", file_name); 
 } 
 close(server_socket_fd); 
 return 0; 
} 


在这里面,有几个东西要讲下:

1.struct sockaddr_in与struct sockaddr区别和联系

先看下这两个结构体的内部参数吧:

include 

struct sockaddr {
    unsigned short    sa_family;    // 2 bytes address family, AF_xxx
    char              sa_data[14];     // 14 bytes of protocol address
};

// IPv4 AF_INET sockets:

struct sockaddr_in {
    short            sin_family;       // 2 bytes e.g. AF_INET, AF_INET6
    unsigned short   sin_port;    // 2 bytes e.g. htons(3490)
    struct in_addr   sin_addr;     // 4 bytes see struct in_addr, below
    char             sin_zero[8];     // 8 bytes zero this if you want to
};

struct in_addr {
    unsigned long s_addr;          // 4 bytes load with inet_pton()
};

其实二者内存的大小是一样的,在sockadr_in里面有个参数sin_zero就是为了让二者的内存大小都是16而增加的,他里面其实都是0,所以二者可以互相转换,并且在初始化赋值时会选用sockaddr_in来赋值(因为简单),然后再把整个结构体(sockaddr*)转换成sockaddr类型的。那为什么要转成sockaddr类型的呢?


下面给你答案:sockaddr常用于bind、connect、recvfrom、sendto等函数的参数,指明地址信息。是一种通用的套接字地址。而sockaddr_in 是internet环境下套接字的地址形式。所以在网络编程中我们会对sockaddr_in结构体进行操作。使用sockaddr_in来建立所需的信息,最后使用类型转化就可以了。下面是一个完整的例子。


2.第二个问题:htonl()是什么意思,起着什么作用,为什么需要它?

首先,htonl---host to network unsigned long-----将本机字节顺序转为网络字节顺序,那什么是本机字节顺序,什么又是网络字节顺序呢

网路字节顺序就是先把高位的数据存到地址中,我们这边默认地址是从低到高依次存储的,其实和平常所说的大端模式的存储方式一样。比如0x12345678, 先把12存到0x00里面,然后再把34存到0x01里面。-------采用统一的顺序,可以解决兼容性的问题-------http://www.cnblogs.com/wxxweb/archive/2011/06/28/2092108.html

那本机字节顺序根据不同的机器会不一样,不过大多都是和网络字节顺序相反的,所以需要用htonl函数来转化。具体的讲解可以看http://blog.csdn.net/yaxiya/article/details/6722083

一般的电脑都是小段模式,可以随便看一个可执行程序  readelf -e a.out可以看出elf文件的头里面有个参数是little endian,所以一般的电脑的端口转换为网络的字节顺序时都要用htonl函数。

那为什么会有大小端模式之分呢-------简单来说就是一个字节是不存在大小端之分的,每个地址单元都对应着一个字节,一个字节为8bit。
但是在C语言中除了8bit的char之外,还有16bit的short型,
32bit的long型(要看具体的编译器),所以当多个字节时就存在字节之间的摆放问题,是你先放在低地址还是我放在低地址。具体可看:http://blog.csdn.net/loveprogram_1/article/details/30464761


3.socket()函数参数的设置,代表的意思是什么?

int socket(int family, int type, int protocol)

family-----这个参数指定一个协议簇,也往往被称为协议域,系统存在许多可以的协议簇,常见有AF_INET──指定为IPv4协议,AF_INET6──指定为IPv6,AF_LOCAL──指定为UNIX 协议域等等。目前仅支持AF_INET格式

type-------这个参数指定一个套接口的类型,套接口可能的类型有:SOCK_STREAM、SOCK_DGRAM、SOCK_SEQPACKET、SOCK_RAW等等

SOCK_STREAM 提供有序的、可靠的、双向的和基于连接的字节流,使用带外数据传送机制,为Internet地址族使用TCP。
SOCK_DGRAM 支持无连接的、不可靠的和使用固定大小(通常很小)缓冲区的 数据报服务,为Internet地址族使用UDP。
那这里又涉及到了TCP与UDP的区别:简单来说就是TCP事先必须建立连接,通过各种校验/发送失败重发来保证数据发送的正确性,而UDP他不建立连接,有点像广播,不保证发送的准确达到,也没有重发的机制,具体可见:http://blog.csdn.net/yipiankongbai/article/details/24435977


4.对于server_addr.sin_addr.s_addr = htonl(INADDR_ANY)是绑定IP地址,那么INADDR_ANY什么意思呢?

作为服务器,你要绑定【bind】到本地的IP地址上进行监听【listen】,但是你的机器上可能有多块网卡,也就有多个IP地址,这时候你要选择绑定在哪个IP上面,如果指定为INADDR_ANY,那么系统将绑定默认的网卡【即IP地址】。
那我如果想绑定一个特定的IP呢,可以这样做:
 
    
原型:in_addr_t inet_addr(const char *cp);
参数:字符串,一个点分十进制的IP地址
返回值:
如果正确执行将返回一个无符号长整数型数。如果传入的字符串不是一个合法的IP地址,将返回INADDR_NONE。
头文件:
Winsock2.h (windows)
arpa/inet.h (Linux)

server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");

那这里如何看主机的地址呢,以linux为例:那为什么是下面这个呢?困了,先眯会,下午见
 
    5.在数据传输上使用recvfrom和sendto函数,那这两个函数各参数的含义是什么? 
   

在无连接的数据报socket方式下,由于本地socket并没有与远端机器建立连接,所以在发送数据时应指明目的地址

sendto()函数原型为:

int sendto(int sockfd, const void *msg,int len unsigned int flags, const struct sockaddr *to, int tolen);
该函数比send()函数多了两个参数,to表示目地机的IP地址和端口号信息,而tolen常常被赋值为sizeof (struct sockaddr)。Sendto 函数也返回实际发送的数据字节长度或在出现发送错误时返回-1。 
  recvfrom()函数原型为: 

int recvfrom(int sockfd,void *buf,int len,unsigned int lags,struct sockaddr *from,int *fromlen);

from是一个struct sockaddr类型的变量,该变量保存源机的IP地址及端口号。fromlen常置为sizeof (struct sockaddr)。当recvfrom()返回时,fromlen包含实际存入from中的数据字节数。Recvfrom()函数返回接收到的字节数或当出现错误时返回-1,并置相应的errno。 



下面是client的代码,已经测试过:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include

#define SERVER_PORT 8000
#define BUFFER_SIZE 1024
#define DATA_LEN 10
int main()
{
  /*creat a UDP interface*/
 struct sockaddr_in server_addr;
 bzero(&server_addr,sizeof(server_addr));
 server_addr.sin_family = AF_INET;
 server_addr.sin_port = htons(SERVER_PORT);
 server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
  
  /*creat socket interface*/
  int socket_client_fd = socket(AF_INET,SOCK_DGRAM,0);
  if(socket_client_fd == -1)
  {
	  perror("Creat Socket Failed :");
	  exit(1);
  }
  
 
  /* data transport */
  while(1)
  {
	  char data[DATA_LEN];
	  bzero(data,DATA_LEN);
	  printf("please input the data which you want to tran : ");
	  scanf("%s",data);
	  //strncpy()
	  if(sendto(socket_client_fd,data,DATA_LEN,0,(struct sockaddr*)&server_addr,sizeof(server_addr)) < 0)
	  {
		  perror("Send Data Failed :");
		  exit(1);
	  }
	  
  }
  close(socket_client_fd);

  return 0;
}



总结:

在server端过程如下:

配置UDP 端口的信息--->创建socket--->绑定socket和UDP端口--->接收数据--->关闭socket


在client端与server端有点不一样,过程如下:

配置要发送的UDP端口信息--->创建socket--->通过socket往UDP端口发送数据--->关闭socket


二者区别在于client不用将socket与UDP端口绑定,而server需要绑定呢,为什么?

其实是因为client的UDP是server的信息,你要发送一个东西只需通过socket把msg传递到对方的IP(UDP端口)上,

而由于在server端可能有多个网口也就是多个IP,那么我们接受数据就得指定一个特定的IP,需要通过socket去拿特定IP端口的msg。

网络上有个这样的比喻;Socket的英文原义是“孔”或“插座”。作为BSD UNIX的进程通信机制,取后一种意思。通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄,可以用来实现不同虚拟机或不同计算机之间的通信。在Internet上的主机一般运行了多个服务软件,同时提供几种服务。每种服务都打开一个Socket,并绑定到一个端口上,不同的端口对应于不同的服务。Socket正如其英文原意那样,像一个多孔插座。一台主机犹如布满各种插座的房间,每个插座有一个编号









你可能感兴趣的:(网络)