在网络传输协议中,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等等
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");
在无连接的数据报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。
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正如其英文原意那样,像一个多孔插座。一台主机犹如布满各种插座的房间,每个插座有一个编号