TCP/UDP对比
1.TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据前,不需要建立连接
2.TCP提供可靠的服务,也就是说通过TCP连接传送的数据是无差错,不丢失,不重复且按序到达的;UDP是尽最大努力交付,即保证可靠交付。
3.TCP是面向字节流,实际上是TCP把数据看成是一连串无结构的字节流;UDP是面向报文的,UDP没有拥塞控制,因此网络出现拥塞不会是源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议…)
4.每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信
5.TCP的首部开销20字节;UDP的首部开销小,只有8个字节
6.TCP是逻辑通信信道是全双工的可靠信道;UDP是不可靠信道
端口号作用
一台拥有IP地址的主机可以提供许多服务,比如Web服务、FTP服务、SMTP服务等
这些服务完全可以通过I个IP地址来实现。那么。主机是怎样区分不同的网络服务呢?显然不能只靠P地址,因为IP地址与网络服务的关系是一对多的关系。
实际上是通过IP地址+端口号"来区分不同的服务的。端口提供了—种访向通道,服务器―般都是通过知名端口号来识别的。例如,对于每个TCP/IP实现来说,FTP服务器的TCP端口号都是21,每个Telnet服务器的TCP端口号都是23,每个TFTP(简单文件传送协议)服务器的UDP端口号都是69
字节序
字节序,即字节在电脑中存放时的序列与输入(输出)时的序列是先到的在前还是后到的在前。
1. Little endian:将低序字节存储在起始地址
2. Big endian:将高序字节存储在起始地址
网络字节序=大端字节序
socket服务器和客户端的开发步骤
上方这幅图可概括为:
1.创建套接字
2.为套接字添加信息(IP地址和端口号)
3.监听网络连接
3.监听到有客户端接入,接受一个连接
5.进行数据交互
6.关闭套接字,断开连接
socket函数的介绍
1.socket()
头文件:
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h> //man手册可查
函数原型:
int socket(int domain, int type, int protocol);
int domain:指明所使用的协议族,通常为AF_INET,表示互联网协议族(TCP/IP协议族)
AF_UNIX, AF_LOCAL Unix域
AF_INET IPv4 因特网域
AF_INET6 IPv6 因特网域
AF_ROUTE 路由套接字
AF_KEY 密钥套接字
AF_UNSPEC 未指定
int type:指定socket的类型
SOCK_STREAM:流式套接字提供的可靠的,面向连接的通信流;它使用TCP协议,从而保证了数据传输的正确性和顺序性
SOCK_DGRAM:数据报套接字定义了一种无连接的服务,数据报通过相互独立的报文进行传输,是无序的,并且不保证是可靠,无差错的。它使用的数据报协议是UDP
SOCK_RAW:允许程序使用底层协议,原始套接字允许对底层协议如IP或ICMP进行直接访问,功能强大但是使用较为不方便,主要用于一些协议的开发。
int protocol:通常赋值为0;
0:表示自动选择type类型对应的默认协议
IPPROTO_TCP TCP传输协议
IPPROTO_UDP UDP传输协议
IPPROTO_SCTP SCTP传输协议
IPPROTO_TICP TICP传输协议
2.bind()
功能:用于绑定IP地址和端口号到 socketfd
头文件:
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
函数原型:
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
int sockfd:是一个socket描述符
const struct sockaddr *addr:是一个指向包含有本机IP地址及端口号等信息的sockaddr类型的结构体指针,指向要绑定给sockfd的协议地址结构,这个地址结构根据地址创建socket时的地址协议族的不同而不同
struct sockaddr {
sa_family_t sa_family;//协议族
char sa_data[14];//IP+端口号
}//可同等替换为下面这个
struct sockaddr_in {//如何找到这个结构体,在下方有详解
__kernel_sa_family_t sin_family; //协议族
__be16 sin_port; //端口号
struct in_addr sin_addr;//IP地址结构体 ,
unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) -sizeof(unsigned short int) - sizeof(struct in_addr)];/*填充 没有实际意义,只是为跟sockaddr结构在内存在内存中对其,这样两者才能相互*/
};
socklen_t addrlen:数据大小(sizeof(struct sockaddr_in))
如何找到到struct sockaddr_in这个结构体和struct in_addr sin_addr这个结构体
1.cd /usr/include/
2.grep “struct sockaddr_in {” * -nir (n表示显示行号,i表示不区分大小写)
找他struct in_addr sin_addr这个结构体用同样的方法
3.地址转换API
int inet_aton(const char *cp, struct in_addr *inp);
把字符串形式的IP地址如"192.168.1.123"装换为网络能识别的格式
const char *cp:为你的IP地址
struct in_addr *inp:存放你这个IP底盘指针结构体(在上面bind()中有这个结构体),例如:&s_addr.sin_addr
char *inet_ntoa(struct in_addr in);
把网络格式的IP地址转换成字符串形式
ex:inet_ntoa(c_addr.sin_addr)
4.listen()
功能:监听设置函数,只能用于服务端,为了接受连接,先用socket()创建一个套接口的描述字,然后用listen()创建套接口并为申请进入的连接建立一个后备日志,然后便可用accept()接受连接了。listen()仅适用于支持连接的套接口,如SOCK_STREAM类型的。套接口s处于一种“变动”模式,申请进入的连接请求被确认,并排队等待被接受。这个函数特别适用于同时有多个连接请求的服务器;如果当一个连接请求到来时,队列已满,那么客户将收到一个WSAECONNREFUSED错误。
当没有可用的描述字时,listen()函数仍试图正常地工作。它仍接受请求直至队列变空。当有可用描述字时,后续的一次listen()或accept()调用会将队列按照当前或最近的“后备日志”重新填充,如有可能的话,将恢复监听申请进入的连接请求。
函数原型:
int listen(int sockfd, int backlog);
int sockfd:是一个socket描述符
int backlog:等待连接队列的最大长度。
5.accept()
功能:accept函数由 TCP服务器调用,用于从已完成连接队列队头返回下一个已完成连接。如果已完成连接队列为空,那么进程被投入睡眠。
函数原型:
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
int sockfd:是一个socket描述符
struct sockaddr *addr:用来返回已连接的对端(客户端)的协议地址,也就是客户端地址,(struct sockaddr *)&c_addr
socklen_t *addrlen:客户端地址长度, int clen = sizeof(struct sockaddr_in);
6.connect()
功能:客户端连接主机
函数原型:
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
int sockfd:目的服务器的文件描述符
const struct sockaddr *addr:是服务器端的IP地址和端口号的地址结构指针,(struct sockaddr *)&c_addr
socklen_t addrlen:地址长度,常被设置为sizeof(struct sockaddr_in);
//成功返回0 失败返回-1 并且error中包含相应的错误码
7.字节序转换API
#include <arpa/inet.h>//头文件
uint32_t htonl(uint32_t hostlong);//返回网络字节序的值
uint16_t htons(uint16_t hostshort);//返回网络字节序的值
uint32_t ntohl(uint32_t netlong);//返回主机字节序的值
uint16_t ntohs(uint16_t netshort);//返回主机字节序的值
/*h代表host,n代表net,s代表short(两个字节),l代表long(四个字节),通过上面四个函数可以实现主机和网络字节序的之间的转换。有时可以用INADDR_ANY,INADDR_ANY指定地址让操作系统之间获取*/
代码示例:
服务端代码 sever.c
#include<stdio.h>
#include<stdlib.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
//#include<linux/in.h> 与netinet/in.h冲突:了
#include <arpa/inet.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include<string.h>
int main(int argc, char **argv)
{
//1.socket
int s_fd;
int c_fd;
char Readbuf[128];
int n_read;
char msg[128]={0};
// char *msg ="I get you connet";
struct sockaddr_in s_addr;
struct sockaddr_in c_addr;
if(argc != 3){
printf("progam is wrong\n");
}
memset(&s_addr,0,sizeof(struct sockaddr_in));
memset(&c_addr,0,sizeof(struct sockaddr_in));
s_fd=socket(AF_INET,SOCK_STREAM,0);
if(s_fd == -1){
perror("socket");
exit(-1);
}
s_addr.sin_family=AF_INET;
s_addr.sin_port=htons(atoi(argv[2]));//端口号需要用htons函数进行转换
inet_aton(argv[1],&s_addr.sin_addr);//地址装换api
//2.bind
bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
//3.listen
listen(s_fd,10);
while(1){
//4.accept
int clen = sizeof(struct sockaddr_in);
c_fd =accept(s_fd,(struct sockaddr *)&c_addr,&clen);
if(c_fd == -1){
perror("accept");
}
printf("get connect is %s \n",inet_ntoa(c_addr.sin_addr));//把网络格式的IP地址装>换成字符串形式
//5.read
if(fork()==0){
if(fork()==0){
while(1){
printf("sever input:");
memset(msg,0,sizeof(msg));
gets(msg);
write(c_fd,msg,strlen(msg));
}
}
while(1){
memset(Readbuf,0,sizeof(Readbuf));
n_read=read(c_fd,Readbuf,128);
if(n_read == -1){
perror("read");
}
else{
if(n_read==0){
exit(0);
}
printf("the message is from clint: %d,%s\n",n_read,Readbuf);
}
}
//6.write
break;
}
}
return 0;
}
客户端代码 clint.c
#include<stdio.h>
#include<stdlib.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
//#include<linux/in.h> 与netinet/in.h冲突:了
#include <arpa/inet.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include<string.h>
int main(int argc, char **argv)
{
//1.socket
int c_fd;
char Readbuf[128];
int n_read;
char msg[128]={0};
//char *msg ="the message is from clint233";
struct sockaddr_in c_addr;
if(argc != 3){
printf("progam is wrong\n");
}
memset(&c_addr,0,sizeof(struct sockaddr_in));
c_fd=socket(AF_INET,SOCK_STREAM,0);
if(c_fd == -1){
perror("socket");
exit(-1);
}
c_addr.sin_family=AF_INET;
c_addr.sin_port=htons(atoi(argv[2]));//端口号需要用htons函数进行转换
inet_aton(argv[1],&c_addr.sin_addr);//地址装换api
//connect
if(connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr_in))==-1){
perror("connect");
exit(-1);
}
while(1){
if(fork()==0){
while(1){
printf("input:");
memset(msg,0,sizeof(msg));
gets(msg);
write(c_fd,msg,strlen(msg));
}
}
while(1)
{
memset(Readbuf,0,sizeof(Readbuf));
n_read=read(c_fd,Readbuf,128);
if(n_read == -1){
perror("read");
}
else{
if(n_read==0){
printf("\n");
exit(0);
}
printf("the message is from sever %d,%s\n",n_read,Readbuf);
}
}
break;
}
return 0;
}
结果示例: