网络编程预备知识
我们知晓了数据如何在各种网络硬件设备中如何流转了,但是是否在我们输入了网址之后,数据就直接进入
到网卡中进行发送了呢?我们发送那串网址有什么特别的含义吗?
输入的那一串是什么?
URL
?
URL
:全称为
“
统一资源定位符
”
,它是互联网上用来标识和定位具体资源的字符串,包括文件、图片、
音频、视频等各种类型。
协议
:是指在通信环境中,用于控制通信过程、传递数据和实现可靠的数据交换服务的一组规则和标
准。在计算机网络中,协议是为了使多个设备在互联网上能够进行互通而制定的。
一个完整的
URL
通常由协议、服务器名、路径和查询参数四个部分组成。
1.1OSI七层模型
OSI
体系结构是第一个标准化的计算机网络体系结构。它是针对广域网通信
(
也就是不同网络之间的通信
)
进行
设计的,将整个网络通信的功能划分为七个层次,由低到高分别是
物理层
(Physical Layer)
、数据链路层
(Data Link Layer)
、网络层
(Network Layer)
、传输层
(Transport Layer)
、会话层
(Session Layer)
、表
示层
(Presentation Layer)
、应用层
( Application Layer)
。
第
7
层应用层:提供为应用软件而设的接口,以设置与另一应用软件之间的通信。例如
: HTTP
,
HTTPS
, FTP,
TELNET
,
SSH
,
SMTP
,
POP3
等。
第
6
层表示层:把数据转换为能与接收者的系统格式兼容并适合传输的格式
第
5
层会话层:负责在数据传输中设置和维护计算机网络中两台计算机之间的通信连接。
第
4
层传输层:把传输表头(
TH
)加至数据以形成数据包。传输表头包含了所使用的协议等发送信息。例如:
传输控制协议(
TCP
)等。
第
3
层网络层:决定数据的路径选择和转寄,将网络表头(
NH
)加至数据包,以形成分组。网络表头包含了网络数据。例如:
互联网协议(
IP
)等。
第
2
层数据链路层:负责网络寻址、错误侦测和改错。当表头和表尾被加至数据包时,会形成帧。数据链表头(DLH)是包含了物理地址和错误侦测及改错的方法。
第
1
层物理层:在局部局域网上传送数据帧(
data frame
),它负责管理计算机通信设备和网络媒体之间的互通。包括了针脚、电压、线缆规范、集线器、中继器、网卡、主机适配器等。
1.2TCP/IP模型
TCP/IP
协议体系结构只划分了四层,从高到低分别是:
应用层(
Apllication Layer
)、传输层(
Transport
Layer
)、网络层(
Network Layer
)和网络接口层
。虽然只有四层,但它却包含了
OSI
中的所有七层的功
能,同样包括了局域网和广域网通信所需要的全部功能。目前
tcp/ip
模型应用比较广泛,而
OSI
七层模型由
于过于理想化且实现较为复杂所有并没有成为主流模型。
1.3 三次握手
TCP
是一个面向连接的协议,三次握手就是建立连接的过程,连接过程如下图:
第一次握手
:首先客户端向服务器端发送一段
TCP
报文,其中:标记位为
SYN
,表示
“
请求建立新连接
”;
序号为seq=X
;随后客户端进入
SYN-SENT
阶段。
第二次握手
:服务器端接收到来自客户端的
TCP
报文之后,结束
LISTEN
阶段。并返回一段
TCP
报文,其中:标志位为SYN
和
ACK
,表示
“
确认客户端的报文
Seq
序号有效,服务器能正常接收客户端发送的数据,并同意创建新连接”
(即告诉客户端,服务器收到了你的数据);序号为
Seq=y
;确认号为
ack=x+1
,表示收到客户端的序号seq
并将其值加
1
作为自己确认号
Ack
的值;随后服务器端进入
SYN-RCVD
阶段。
第三次握手
:客户端接收到来自服务器端的确认收到数据的
TCP
报文之后,明确了从客户端到服务器的数据传输是正常的,结束SYN-SENT
阶段。并返回最后一段
TCP
报文。其中:标志位为
ACK
,表示
“
确认收到服务器端同意连接的信号”
(即告诉服务器,我知道你收到我发的数据了);序号为
seq=x+1
,表示收到服务器端的确认号ack
,并将其值作为自己的序号值;确认号为
ack=y+1
,表示收到服务器端序号
seq
,并将其值加1作为自己的确认号
Ack
的值;随后客户端进入
ESTABLISHED
阶段。服务器收到来自客户端的
“
确认收到服务器数据”
的
TCP
报文之后,明确了从服务器到客户端的数据传输是正常的。结束
SYN-SENT
阶段,进入 ESTABLISHED阶段。
1.4超时重传
网络环境是错综复杂的,网络数据除了可能以乱序的方式到达,也可能到达不了,对于这种情
况,
TCP
设计了超时重传机制。
当报文发出后在一定的时间内未收到接收方的确认,发送方就会进行重传
(通常是在发出报文段后设定一个闹钟,到点了还没有收到应答则进行重传),其基本过程如下:
1.5四次挥手
在建立连接阶段
TCP
设计了三次握手来保证可靠传输,那么在通信结束时是否它也有这样的方式来进行断开
连接呢?断开连接过程如下图:
第一次挥手
:客户端进程发出连接释放报文,并且停止发送数据。释放数据报文首部,
FIN=1
,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加
1
),此时,客户端进入
FIN-WAIT-1
(终止等待1
)状态。
TCP
规定,
FIN
报文段即使不携带数据,也要消耗一个序号。
第二次挥手
:服务器收到连接释放报文,发出确认报文,
ACK=1
,
ack=u+1
,并且带上自己的序列号 seq=v,此时,服务端就进入了
CLOSE-WAIT
(关闭等待)状态。
TCP
服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数 据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。
客户端收到服务器的确认请求后,此时,客户端就进入
FIN-WAIT-2
(终止等待
2
)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。
第三次挥手
:服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,
FIN=1
,
ack=u+1
,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w
,此时,服务器就进入了
LAST-ACK(最后确认)状态,等待客户端的确认。
第四次挥手
:客户端收到服务器的连接释放报文后,必须发出确认,
ACK=1
,
ack=w+1
,而
自己的序列号是
seq=u+1
,此时,客户端就进入了
TIMEWAIT
(时间等待)状态。注意此时
TCP
连接还没有释放,必须经过 2
∗
MSL
(最长报文段寿命)的时间后,当客户端撤销相应的
TCB
后,才进入CLOSED状态。
服务器只要收到了客户端发出的确认,立即进入
CLOSED
状态。同样,撤销
TCB
后,就结束了这次的
TCP连接。可以看到,服务器结束TCP
连接的时间要比客户端早一些。
2.网络编程
2.1socket套接字
Socket是一个用于网络通信的技术。Socket通信允许客户端——服务器之间进行双向通信。他可以使任何客户端机器连接到任何服务器,安装在客户端和服务器两侧的程序就可以实现双向的通信。Socket的作用就是把连接两个计算机的通信软件“中间接”起来,能够实现远程连接。
socket
是一个抽象层,应用程序可以通过它发送或接收数据,可对其进行像对文件一样的打开、读写和关闭等操作。socket
允许应用程序将
I/O
插入到网络中,并与网络中的其他应用程序进行通信。 为了满足不同的通信程序对通信质量和性能的要求,一般的网络系统提供了三种不同类型的套接字,以供用户在设计网络应用程序时根据不同的要求来选择。这三种套接为流式套接字(
SOCK-STREAM
)
、
数据报套
接字(
SOCK-DGRAM
)
和
原始套接字(
SOCK-RAW
)
。
(
1
)
流式套接字
:它提供了一种可靠的、面向连接的双向数据传输服务,实现了数据无差错、无重复的发送。流式套接字内设流量控制,被传输的数据看作是无记录边界的字节流。在TCP/IP
协议簇中,使用
TCP
协议来实现字节流的传输,当用户想要发送大批量的数据或者对数据传输有较高的要求时,可以使用流式套接字。
(
2
)
数据报套接字
:它提供了一种无连接、不可靠的双向数据传输服务。数据包以独立的形式被发送,并且保留了记录边界,不提供可靠性保证。数据在传输过程中可能会丢失或重复,并且不能保证在接收端按发送顺序接收数据。在TCP/IP
协议簇中,使用
UDP
协议来实现数据报套接字。在出现差错的可能性较小或允许部分传输出错的应用场合,可以使用数据报套接字进行数据传输,这样通信的效率较高。
3
)
原始套接字
:该套接字允许对较低层协议(如
IP
或
ICMP
)进行直接访问,常用于网络协议分析,检验新的网络协议实现,也可用于测试新配置或安装的网络设备。
2.2TCP编程流程
网络程序结构一般为CS结构,即server:服务器,client:客户端。
tcp编程接口以及流程如下:
TCP Server:服务端
socket: 创建一个套接字
bind: 把一个套接字和网络地址绑定在一起。
如果你想让其他人来主动链接或联系你,你就需要bind一个地址,并且把这个地址告诉其他人。 如果不调用bind,并不不代表你的socket就没有地址,相反你调不调用bind,socket在通信时,内核都会动态为你的socket指定一个地址。
listen: 让套接字进入一个“监听模式”
accept: 接受客户端的连接, 多次调用accept就可以与不同的客户建立连接。
write/send/sendtoor read/recv/recvfrom:数据收发
close/shutdown: 断开连接,"四路挥手
"
TCP Client
:客户端
socket
bind:可要可不要
connect: 主动与
TCP Server
建立连接
--->"
三次握手
"
write/send/sendtoor read/recv/recvfrom:数据收发
close/shutdown: 断开连接,"
四路挥手
"
2.3tcp相应API
创建一个套接字:socket
#include<sys/types.h> /*SeeNOTES*/
#include<sys/socket.h>
intsocket(intdomain,inttype,intprotocol);
domain:指定域/协议族
socket接口不仅仅局限于TCP/IP,他还可以本地通信...
每一种通信模式下面都有一系列自己的协议,归到一类:协议族
AF_UNIX AF_LOCAL Localcommunication unix(7)
AF_INET IPv4Internetprotocols ipv4(7)
AF_INET6 IPv6Internetprotocols ipv6(7)
type:套接字类型
SOCK_STREAM 流式套接字 用于TCP
SOCK_DGRAM 数据报套接字 用于UDP
SOCK_RAW 原始套接字 用于本地域协议
protocol:指定具体的应用层协议,一般为0,代表不知名的私有协议
返回值:
成功返回套接字描述符(特殊的文件描述符)
失败返回-1
(2)网络地址
我们日常口头上说的网络地址一般是指IP地址
但是在socket编程中,规范的讲:网络地址是一个结构体
不同的协议族,他的网络地址结构体是不一样
socket编程接口,提供了一个通用的网络地址结构体
structsockaddr{
sa_family_tsa_family;//指定协议族 char sa_data[14];
//没有确定的含义(不同的协议族有不同的含义), //只是占用14个字节
};
ipv4协议族的网络地址结构体
定义在#include<netinet/in.h>
structsockaddr_in {
sa_family_tsin_family;//指定协议族
u_int16sin_port;//端口号
structin_addrsin_addr;//IPV4地址
unsignedcharsin_zero[8];//填充8个字节,为了和通用网络地址结构体一样大
};
structin_addr {
in_addr_ts_addr;
};
IP地址之间的转换函数
192.168.31.135"
点分式 《-》 in_addr_t/structin_addr
ip格式转换:
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
intinet_aton(constchar*cp,structin_addr*inp);
把cp指向的“点分式字符串”转换为structin_addr
char *inet_ntoa(struct in_addr in);
in_addr_t inet_addr(const char *cp);
in_addr_t inet_network(const char *cp);
绑定IP地址:bind
把一个套接字和一个“网络地址”绑定起来
int bind (int sockfd,const struct sockaddr *addr,socklen_t addrlen);
sockfd:套接字描述符,也就是socket的返回值
addr:网络地址接狗日的地址
addrlen:指定网络地址结构提的长度
返回值:
成功返回0
失败返回-1,同时errno被设置
例如:
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if()
{
....
}
struct sockaddr_in addr;//定义一个ipv4网络地址
addr.sin_family = AF_INET;//协议族
addr.sin_port = htons(1234);//端口号
inet_aton("192.168.31.145",&addr.sin_addr);//给IP地址赋值
memset(addr.sin_zero,0,8);//把填充区置0
bind(sockfd,&addr,sizeof(addr));
if()
{
...
}
不调用bind,并不代表你的socket就没有地址,相反你调不调用bind,socket在通信时,内核都会动态为你的socket制定一个地址
监听模式:listen
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int listen(int sockfd, int backlog);
sockfd:套接字描述符,也就是socket的返回值
backlog: 同时能够处理连接请求的客户端个数
/*
backlog 值的含义:内核为每一个监听套接字 维护两个队列
未完成连接队列:这个队列中的套接字处于SYN_ECVD状态,即已有某个客户发出并达到服务器,而服务器正在等待完成相应的TCP三路握手过程
已完成连接队列:每个已完成TCP三路握手过程的客户对应其中一项。这些套接字处于ESTABLISHED状态。
这两个队列中的连接数之和 和 backlog 有相应的关系。
不同的标准有不同的解释。
*/
返回值:
成功返回0
失败返回-1,同时errno被设置
监听套接字和链接套接字的区别:
监听套接字处于listening状态,监听套接字由本地IP+端口 二元组标识
链接套接字处于established状态,链接套接字由本地ip+端口+客户端ip+端口 四元组标识
等待客户端连接:accept
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr,socklen_t *addrlen);
sockfd:套接字描述符,也就是socket的返回值
addr:网络地址结构体的地址,accept成功后,该结构体就保存了请求连接的客户端的网络地址
如果不想知道是那个客户端,就可以不保存,这个参数为NULL
addrlen:网络地址结构体长度类型的指针,事先保存第二个参数指向的结构体的长度(目的只为了防止越界)
accept成功之后,保存的是真正的客户端网络地址长度
返回值:
成功返回一个新的文件描述符,这个文件描述符就是
后续用来与客户端进行通信的
向服务器发起连接请求:connect
客户端主动向服务端发送连接请求,客户端需要事先知道服务端的“网络地址”
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd :套接字文件描述符,socket的返回值
addr: 您要去连接的那个服务端的网络地址
addrlen:addr的长度,防止越界
返回值:
成功返回0
失败返回-1
关闭连接:close shutdown
close
关闭读和写
shutdown - shut down part of a full-duplex connection
关闭部分全双工连接
#include <sys/socket.h>
int shutdown(int sockfd, int how);
sockfd:文件描述符
how:关闭模式
SHUT_RD :只关闭读端
SHUT_WR :只关闭写端
SHUT_RDWR :读写都关闭
返回值:
成功,返回0
失败,返回-1
读写接口:
write / send / sendto
这三个函数,
TCP
都可以用。
UDP
只能用
sendto
。
read / recv / recvfrom
这三个函数,
TCP
都可以用,
UDP
只能用
recvfrom
。
read / write
的就是我们之前学习的文件
IO
函数。
因为
socket
是一个特殊的文件描述符,所以也可以使用
read / write
。
ssize_t send(int sockfd,const void *buf,size_t len,int flags);
sockfd:你要往哪一个套接字上面发送数据
buf:你要发送的数据的指针
len:你要发送的数据的长度
send的前面三个参数与write类似
flags:一般为0;
返回值:
成功返回实际发送的字节数,
失败返回-1,同时errno 被设置
ssize_t sendto(int sockfd,const viod *buf,size_t len,int flags,const struct
sockaddr *dest_addr,socklen_t addrlen);
sendto和send类似,只不过多了两个参数。
dest_addr:指定接收方的地址
如果是TCP协议通信,此处可以省略,因为TCP是面向连接的通信,意味着TCP在发送数据之前就已经和对方
建立连接了(已经知道对方的地址了)。而UDP是无连接的通信,UDP在发送数据的时候并不知道对方的地址,
所以需要指定。
addrlen:dest_addr指向的那个地址结构体的长度。
返回值:
成功返回实际发送的字节数,
失败返回-1,同时errno 被设置。
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
recv前面的三个参数和read类似,都是从指定的文件描述符中读取count个字节到buf指向的空间中
去。
flags:一般为0。
返回值:
成功返回实际接收到的字节数,
失败返回-1,同时errno 被设置。
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr
*src_addr, socklen_t *addrlen);
前面三个参数同recv一致。
src_addr:用来保存发送方的地址。
TCP可以不用管,因为TCP肯定知道数据来源于谁。所以直接填NULL即可。
而UDP的话,也可以填NULL,表示不关心是谁发送的,但是并不影响别人给他发送数据。
addrlen:用来保存发送者地址信息的实际的长度的。
返回值:
成功返回实际接收到的字节数
失败返回-1,同时 errno被设置。
示例代码
:
tcp_server :
服务端代码
//./a.out port ip
int main(int argc,char *argv[])
{
if(argc != 3)
{
printf("Usage : %s <PROT> <IP>\n",argv[0]);
return -1;
}
int sock = create_socket(atoi(argv[1]),argv[2]);
if(sock == -1)
{
printf("create socket failed!\n");
return -1;
}
while(1)
{
//每当一个客户端申请与服务器端连接时,开辟一个进程
//去处理当前客户端与服务器的通信,而主线程继续监听
struct sockaddr_in client;
socklen_t len = sizeof(client);
int confd = accept(sock,(struct sockaddr *)&client,&len);
if(confd == -1)
{
perror("accept failed");
continue;
}
printf("connect %s [port : %d]\n",
inet_ntoa(client.sin_addr),ntohs(client.sin_port));
//创建一个新的进程去跟当前客户端通信
pid_t pid = fork();
if(pid == 0)//子进程
{
handle_tcp_client(confd);
exit(0);
}
else if(pid > 0)
{
close(confd);//关闭父进程中的套接字
}
else
{
close(confd);
perror("fork failed");
continue;
}
}
}
//根据端口号和IP地址创建一个套接字
int create_socket(short port,char *ipstr)
{
//1.创建一个套接字
int sock = socket(AF_INET,SOCK_STREAM,0);
if(sock == -1)
{
perror("create socket failed");
return -1;
}
//2.指定本机的IP地址:IP+PROT
struct sockaddr_in local;
local.sin_family = AF_INET; //指定协议族
local.sin_port = htons(port); //指定端口号
local.sin_addr.s_addr = inet_addr(ipstr);//指定IP地址
//3.将地址绑定到套接字上去
int ret = bind(sock,(struct sockaddr *)&local,sizeof(local));
if(ret == -1)
{
perror("bind failed");
goto err_return;
}
//4.监听套接字
ret = listen(sock,10);
if(ret == -1)
{
perror("listen failed");
goto err_return;
}
return sock;//返回一个监听套接字
err_return:
close(sock);
return -1;
}
//处理连接客户端
void handle_tcp_client(int confd)
{
char buf[1024];
int ret;
while(1)
{
ret = recv(confd,buf,1024,0);
if (ret > 0)
{
buf[ret] = '\0';
printf("Recv data: %s\n",buf);
char *resp = "recv your data:";
send(confd,resp,strlen(resp),0);
send(confd,buf,ret,0);
}
}
}
tcp_client :
客户端代码
//./a.out prot ip
int main(int argc,char *argv[])
{
//1.创建一个套接字
int sock = socket(AF_INET,SOCK_STREAM,0);
if(sock == -1)
{
perror("create socket failed");
return -1;
}
//指定要连接的服务器端的地址:IP+PROT
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(atoi(argv[1]));
server.sin_addr.s_addr = inet_addr(argv[2]);
//2.根据IP和端口号连接指定计算机的端口
int ret = connect(sock,(struct sockaddr *)&server,sizeof(server));
if(ret == -1)
{
perror("connect server failed");
return -1;
}
char buf[1024] = {0};
char mess[] = "hello,socket!";
while(1)
{
//3.通过套接字向服务器端发送信息
send(sock,mess,sizeof(mess),0);
//接收服务器回给我们的数据
recv(sock,buf,1024,0);
printf("%s\n",buf);
sleep(2);
}
//4.关闭套接字
close(sock);
}
原则上,只要能够互相
ping
通的两台计算机就可以通过如上代码进行通信。
htons
: 整数在主机字节序与网络字节序之间的转换函数
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
h: host n: network
l: long 32bits
s: short 16bits
htonl:把一个32bits的整数的主机字节序转换成网络字节序
htons:把一个16bits的整数的主机字节序转换成网络字节序
ntohl:把一个32bits的整数的网络字节序转换成主机字节序
ntohs:把一个16bits的整数的网络字节序转换成主机字节序
例子:
serv.sin_port = htons(666);
3.4 udp
编程流程
udp
不同于
tcp
,它提供的是无连接的、不可靠的,数据报的传输层协议。
UDP
协议会尽最大努力去交付数 据,但是不能够保证可靠交付,如果我们需要使用udp
进行可靠传输,就需要在应用层加一些保证传输可靠 的“
控制协议
”
。
代码示例:
udp_server
:
//./a.out ip prot
int main(int argc,char *argv[])
{
if(argc != 3)
{
printf("Usage : %s <IP> <PROT>\n",argv[0]);
return -1;
}
int sock = create_udp_socket(argv[1],atoi(argv[2]));
if(sock == -1)
{
printf("create udp socket failed\n");
return -1;
}
while(1)
{
struct sockaddr_in udp_client;
socklen_t addrlen = sizeof(udp_client);
char buf[1024] = {0};
//接收客户端发送的数据
int ret = recvfrom(sock,buf,1023,0,(struct sockaddr
*)&udp_client,&addrlen);
if(ret > 0)
{
buf[ret] = '\0';
printf("receive data [%s] from [%s : %d]\n",buf,
inet_ntoa(udp_client.sin_addr),ntohs(udp_client.sin_port));
int s = sendto(sock,"OK",2,0,(struct sockaddr
*)&udp_client,addrlen);
if(s == -1)
{
perror("sendto failed");
}
}
sleep(2);
}
close(sock);
}
//成功返回新创建的udp套接字描述符,失败返回-1
int create_udp_socket(char *ipstr,short port)
{
//1.创建一个UDP类型的socket
int sock = socket(AF_INET,SOCK_DGRAM,0);
if(sock == -1)
{
perror("socket failed");
return -1;
}
//2.将socket绑定一个地址
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(port);
server.sin_addr.s_addr = inet_addr(ipstr);
int ret = bind(sock,(struct sockaddr *)&server,sizeof(server));
if(ret == -1)
{
perror("bind failed");
close(sock);
return -1;
}
return sock;
}
udp_client
:
//成功返回新创建的udp套接字描述符,失败返回-1
int create_udp_socket(void)
{
//1.创建一个UDP类型的socket
int sock = socket(AF_INET,SOCK_DGRAM,0);
if(sock == -1)
{
perror("socket failed");
return -1;
}
return sock;
}
int main(int argc,char *argv[])
{
int sock = create_udp_socket();
if(sock == -1)
{
printf("create udp socket failed");
return -1;
}
struct sockaddr_in udp_server;
udp_server.sin_family = AF_INET;
udp_server.sin_port = htons(atoi(argv[2]));
udp_server.sin_addr.s_addr = inet_addr(argv[1]);
socklen_t addrlen = sizeof(udp_server);
unsigned char buf[] = {"Hello,UDP"};
int send = sendto(sock,buf,strlen(buf),0,(struct sockaddr
*)&udp_server,addrlen);
if(send == -1)
{
perror("send failed");
close(sock);
return -1;
}
int ret = recvfrom(sock,buf,sizeof(buf),0,(struct sockaddr
*)&udp_server,&addrlen);
buf[ret] = '\0';
printf("%s\n",buf);
close(sock);
}