1.TCP与UDP在概念上的区别:
Tcp是一种面向连接的,可靠的字节流服务。(设有数据包编号与差错控制机制。)
特点:
由于网络的复杂性,传输信息时,数据包可能会丢失,差错控制中的确认机制在接收到数据包是发送确认信息,若是数据包丢失,则回发数据包编号,让对方重新发送;
由于网络的复杂性,传输信息时有多种网络传送途径可以选择,数据包被接收的顺序与发送顺序不同,可以根据数据包的编号,将数据包重组。
优点:
网络连接是以点对点的形式,加上上述特点,保证了数据的安全性,数据包不会中途被劫。
缺点:耗费资源很多。
UDP是无连接的,不可靠的数据协议报。通讯双方发送数据后不知道对方是否已经收到数据,是否正常收到数据(没有类似TCP的差错控制和数据包编号机制)。
特点:通讯速度比较快,不需要TCP的三次握手。
1.多播:
与发送主机的子网相同的主机,都会在自己的端口收到主机发出来的UDP消息(发送到(包括本机)所有在这个网络地址的主机上,例如192.168.0.255,则在192.168.0.的主机都会收到)。消息会被复制并发到每个主机的网卡上去,网卡收到消息后提交给操作系统去处理,操作系统发现有程序在端口接收UDP数据则把消息转给相应的程序去处理,如果没有程序接收来自该端口的UDP消息,则操作系统丢弃该消息。
2.单播:
则消息只会从主机发到特定的主机上,主机的网卡收到消息后转给操作系统去处理,操作系统再把此消息转给相应程序去处理,如果没有程序处理就丢弃该包。
3.组播:
则消息只会从主机发到加入了规定组的主机的端口。象广播一样,组播消息一样会被复制发到网络所有主机的网卡上,但只有宣布加入这个组的主机的网卡才会把数据提交给操作系统去处理。如果没有加入组,则网卡直接将数据丢弃。要想接收组播消息的主机必须运行命令加入组
2.TCP和UDP编程过程:
TCP握手协议
在TCP/IP协议中,TCP协议采用三次握手建立一个连接。
第一次握手:建立连接时,客户端发送SYN包(SYN=J)到服务器,并进入SYN_SEND状态,等待服务器确认;
第二次握手:服务器收到SYN包,必须确认客户的SYN(ACK=J+1),同时自己也发送一个SYN包(SYN=K),即SYN+ACK包,此时服务器V状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ACK=K+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。
完成三次握手,客户端与服务器开始传送数据,
四次分手:
由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。这个原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个 FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。
(1)客户端A发送一个FIN,用来关闭客户A到服务器B的数据传送。
(2)服务器B收到这个FIN,它发回一个ACK,确认序号为收到的序号加1。和SYN一样,一个FIN将占用一个序号。
(3)服务器B关闭与客户端A的连接,发送一个FIN给客户端A。
(4)客户端A发回ACK报文确认,并将确认序号设置为收到序号加1。
1.为什么建立连接协议是三次握手,而关闭连接却是四次握手呢?
这是因为服务端的LISTEN状态下的SOCKET当收到SYN报文的建连请求后,它可以把ACK和SYN(ACK起应答作用,而SYN起同步作用)放在 一个报文里来发送。但关闭连接时,当收到对方的FIN报文通知时,它仅仅表示对方没有数据发送给你了;但未必你所有的数据都全部发送给对方了,所以你可以 未必会马上会关闭SOCKET,也即你可能还需要发送一些数据给对方之后,再发送FIN报文给对方来表示你同意现在可以关闭连接了,所以它这里的ACK报 文和FIN报文多数情况下都是分开发送的.
2.为什么TIME_WAIT状态还需要等2MSL后才能返回到CLOSED状态?
这是因为虽然双方都同意关闭连接了,而且握手的4个报文也都协调和发送完毕,按理可以直接回到CLOSED状态(就好比从SYN_SEND状态到ESTABLISH状态那样);但是因为我们必须要假想网络是不可靠的,你无法保证你最后发送的ACK报文会一定被对方收到,因此对方处于LAST_ACK状态下的SOCKET可能会因为超时未收到ACK报文,而重发FIN报文,所以这个TIME_WAIT状态的作用就是用来重发可能丢失的ACK报文。
源代码实例:
NO.1 UDP广播实例:
客户端:
include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<netdb.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<sys/stat.h>
#include<errno.h>
#include <pthread.h>
#define RET_OK 0
#define RET_ERR -1
#define LISTEN_QUEUE_NUM 5
#define BUFFER_SIZE 256
#define ECHO_PORT 2029
int sockfd;
struct sockaddr_in servaddr,recvaddr;
pthread_t t1;
void rev()
{
int ret;
char buffer[BUFFER_SIZE];
uint32_t len=sizeof(recvaddr);
while(1)
{
if((ret=recvfrom(sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&recvaddr,&len))<0)
{
perror("ERROR reading from socket");
break;
}
buffer[ret]=0;
printf("[form:%s:%u]%s\n",inet_ntoa(recvaddr.sin_addr),ntohs(recvaddr.sin_port), buffer);
}
}
int main(int argc,char **argv)
{
int opt=1;
int ret=RET_OK;
char buffer[BUFFER_SIZE];
struct hostent *server;
if(argc<2)
{
fprintf(stderr,"usage &s hostname\n",argv[0]);
return RET_ERR;
}
if((server=gethostbyname(argv[1]))==NULL)
{
herror("gethostbyname.");
return RET_ERR;
}
if((sockfd=socket(AF_INET,SOCK_DGRAM,0))<0)
{
herror("ERROR opening socket");
return RET_ERR;
}
//广播设置套接字
if((setsockopt(sockfd,SOL_SOCKET,SO_BROADCAST,&opt,sizeof(opt)))<0)
{
perror("ERROR setsockopt");
goto failed;
}
memset(&servaddr,0,sizeof(servaddr));//申请信息表空间
servaddr.sin_family=AF_INET;//设为IPv4
servaddr.sin_addr.s_addr=*(uint32_t *)server->h_addr;//地址转换,32位大端字序
servaddr.sin_port=htons((uint16_t)ECHO_PORT);//设定端口,16位大端字序转换
pthread_create(&t1, NULL, (void*)rev, NULL);
while(1)
{
printf("Enter the message : ");
if(fgets(buffer,sizeof(buffer)-1,stdin)==NULL)break;
if((ret=sendto(sockfd,buffer,strlen(buffer)+1,0,(struct sockaddr*)&servaddr,sizeof(servaddr)))<0)
{
perror("ERROR writing to socket");
break;
}
}
failed:
close(sockfd);return ret<0?RET_ERR:RET_OK;
}
服务器端:
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<netdb.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<sys/stat.h>
#include<errno.h>
#define RET_OK 0
#define RET_ERR -1
#define LISTEN_QUEUE_NUM 5
#define BUFFER_SIZE 256
#define ECHO_PORT 2029
int main(int agrc,char **argv)
{
int sockfd,opt=1;
uint32_t len;
struct sockaddr_in cliaddr;
uint8_t buffer[BUFFER_SIZE];
int ret=RET_OK;
if((sockfd=socket(AF_INET,SOCK_DGRAM,0))<0)
{
perror("ERROR opening socket");
return RET_ERR;
}
//SO_REUSEADDR允许重用本地址
if((ret=setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt)))<0)
{
perror("ERROR setsockopt");
goto failed;
}
memset(&cliaddr,0,sizeof(cliaddr));
cliaddr.sin_family=AF_INET;
cliaddr.sin_addr.s_addr=INADDR_ANY;
cliaddr.sin_port=htons(ECHO_PORT);
if((ret=bind(sockfd,(struct sockaddr*)&cliaddr,sizeof(cliaddr)))<0)
{
perror("ERROR on binding");
goto failed;
}
do
{
len=sizeof(cliaddr);
if((ret=recvfrom(sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&cliaddr,&len))>0)
{
buffer[ret] = 0;
printf("[%s:%d]%s",inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port),buffer);
ret=sendto(sockfd,buffer,ret,0,(struct sockaddr*)&cliaddr,len);
}
}while(ret>=0);
failed:
close(sockfd);
return 0;
}运行过程:用ifconfig eth0 设置ip,并查看广播地址作为client.c的参数运行;
运行结果:多个服务器可以同时接收来自客户端的信息;
UDP,client中使用connect的情况(differ from TCP):指定接收数据的地址,其他IP地址的数据拒绝接收,
此时,sendto,recvfrom可以用write和read代替.
int sockfd;
struct sockaddr_in servaddr,recvaddr;
pthread_t t1;
void rev()
{
int ret;
char buffer[BUFFER_SIZE];
uint32_t len=sizeof(recvaddr);
while(1)
{
if((ret=read(sockfd,buffer,sizeof(buffer)-1,0))<0)
{
perror("ERROR reading from socket");
break;
}
buffer[ret]=0;
printf("[form:%s:%u]%s\n",inet_ntoa(recva