1.1TCP/IP模型层
(1)应用层{http超文本传输协议;ftp文件传输协议;telnet远程登录;ssh安全外壳协议;stmp简单邮件发送 ;pop3收邮件}
(2)传输层{tcp传输控制协议;udp用户数据包协议}
(3)网络层{ip网际互联协议;icmp网络控制消息协议;igmp网络组管理协议}
(4)网络接口层{arp地址转换协议;rarp反向地址转换协议;mpls多协议标签交换}
(这里说下,有的将模型分为七层,有的五层,也有的四层,这里我自己分为的四层;越前面的层,越靠近用户,越后面的越靠近硬件)
1.2TCP协议与UDP协议的区别
1.2.1 TCP协议概述
(1)TCP是TCP/IP体系中面向连接的运输层协议,它提供全双工和可靠交付的服务。它采用许多机制来确保端到端结点之间的可靠数据传输,如采用序列号、确认重传、滑动窗口等。
首先,TCP要为所发送的每一个报文段加上序列号,保证每一个报文段能被接收方接收,并只被正确的接收一次。
其次,TCP采用具有重传功能的积极确认技术作为可靠数据流传输服务的基础。这里“确认”是指接收端在正确收到报文段之后向发送端回送一个确认(ACK)信息。发送方将每个已发送的报文段备份在自己的缓冲区里,而且在收到相应的确认之前是不会丢弃所保存的报文段的。“积极”是指发送发在每一个报文段发送完毕的同时启动一个定时器,加入定时器的定时期满而关于报文段的确认信息还没有达到,则发送发认为该报文段已经丢失并主动重发。为了避免由于网络延时引起迟到的确认和重复的确认,TCP规定在确认信息中捎带一个报文段的序号,使接收方能正确的将报文段与确认联系起来。
最后,采用可变长的滑动窗口协议进行流量控制,以防止由于发送端与接收端之间的不匹配而引起的数据丢失。这里所采用的滑动窗口协议与数据链路层的滑动窗口协议在工作原理上完全相同,唯一的区别在于滑动窗口协议用于传输层是为了在端对端节点之间实现流量控制,而用于数据链路层是为了在相邻节点之间实现流量控制。TCP采用可变长的滑动窗口,使得发送端与接收端可根据自己的CPU和数据缓存资源对数据发送和接收能力来进行动态调整,从而灵活性更强,也更合理。
(2) 三次握手协议
在利用TCP实现源主机和目的主机通信时,目的主机必须同意,否则TCP连接无法建立。为了确保TCP连接的成功建立,TCP采用了一种称为三次握手的方式,三次握手方式使得“序号/确认号”系统能够正常工作,从而使它们的序号达成同步。如果三次握手成功,则连接建立成功,可以开始传送数据信息。
其三次握手分别为:
1)源主机A的TCP向主机B发送连接请求报文段,其首部中的SYN(同步)标志位应置为1,表示想跟目标主机B建立连接,进行通信,并发送一个同步序列号X(例:SEQ=100)进行同步,表明在后面传送数据时的第一个数据字节的序号为X+1(即101)。
2)目标主机B的TCP收到连接请求报文段后,如同意,则发回确认。再确认报中应将ACK位和SYN位置为1.确认号为X+1,同时也为自己选择一个序号Y。
3)源主机A的TCP收到目标主机B的确认后要想目标主机B给出确认。其ACK置为1,确认号为Y+1,而自己的序号为X+1。TCP的标准规定,SYN置1的报文段要消耗掉一个序号。
运行客户进程的源主机A的TCP通知上层应用进程,连接已经建立。当源主机A向目标主机B发送第一个数据报文段时,其序号仍为X+1,因为前一个确认报文段并不消耗序号。
当运行服务进程的目标主机B的TCP收到源主机A的确认后,也通知其上层应用进程,连接已经建立。至此建立了一个全双工的连接。
三次握手:为应用程序提供可靠的通信连接。适合于一次传输大批数据的情况。并适用于要求得到响应的应用程序。
1.2.2 UDP协议概述
UDP即用户数据报协议,它是一种无连接协议,因此不需要像TCP那样通过三次握手来建立一个连接。同时,一个UDP应用可同时作为应用的客户或服务器方。由于UDP协议并不需要建立一个明确的连接,因此建立UDP应用要比建立TCP应用简单得多。
它比TCP协议更为高效,也能更好地解决实时性的问题。如今,包括网络视频会议系统在内的众多的客户/服务器模式的网络应用都使用UDP协议。
1.2.3 两者之间的区别
TCP协议:传输控制协议 面向连接的协议 能保证传输安全可靠 速度慢(有3次握手)
UDP协议:用户数据包协议 非面向连接 速度快 不可靠
1.3协议之间的选择
(1)对数据可靠性的要求
对数据要求高可靠性的应用需选择TCP协议,如验证、密码字段的传送都是不允许出错的,而对数据的可靠性要求不那么高的应用可选择UDP传送。
(2)应用的实时性
TCP协议在传送过程中要使用三次握手、重传确认等手段来保证数据传输的可靠性。使用TCP协议会有较大的时延,因此不适合对实时性要求较高的应用,如VOIP、视频监控等。相反,UDP协议则在这些应用中能发挥很好的作用。
(3)网络的可靠性
由于TCP协议的提出主要是解决网络的可靠性问题,它通过各种机制来减少错误发生的概率。因此,在网络状况不是很好的情况下需选用TCP协议(如在广域网等情况),但是若在网络状况很好的情况下(如局域网等)就不需要再采用TCP协议,而建议选择UDP协议来减少网络负荷。
2.1套接口的概念
套接口,也叫“套接字”。是操作系统内核中的一个数据结构,它是网络中的节点进行相互通信的门户。它是网络进程的ID。网络通信,归根到底还是进程间的通信(不同计算机上的进程间通信)。在网络中,每一个节点(计算机或路由)都有一个网络地址,也就是IP地址。两个进程通信时,首先要确定各自所在的网络节点的网络地址。但是,网络地址只能确定进程所在的计算机,而一台计算机上很可能同时运行着多个进程,所以仅凭网络地址还不能确定到底是和网络中的哪一个进程进行通信,因此套接口中还需要包括其他的信息,也就是端口号(PORT)。在一台计算机中,一个端口号一次只能分配给一个进程,也就是说,在一台计算机中,端口号和进程之间是一一对应关系。所以,使用端口号和网络地址的组合可以唯一的确定整个网络中的一个网络进程。
例如,如网络中某一台计算机的IP为10.92.20.160,操作系统分配给计算机中某一应用程序进程的端口号为1500,则此时 10.92.20.160 1500就构成了一个套接口。
2.2端口号的概念
在网络技术中,端口大致有两种意思:一是物理意义上的端口,如集线器、交换机、路由器等用于连接其他网络设备的接口。二是指TCP/IP协议中的端口,端口号的范围从0-65535,一类是由互联网指派名字和号码公司ICANN负责分配给一些常用的应用程序固定使用的“周知的端口”,其值一般为0-1023。
例如:http的端口号是80,ftp为21,ssh为22,telnet为23等。(这些都是之前已经固定下来的端口号,而且已经广泛运用,我们不需要去深究)还有一类是用户自己定义的,通常是大于1024的整型值。
2.3 IP地址的概念
通常用户在表达IP地址时采用的是点分十进制表示的数值(或者是为冒号分开的十进制Ipv6地址),而在通常使用的socket编程中使用的则是二进制值,这就需要将这两个数值进行转换。
ipv4地址:32bit, 4字节,通常采用点分十进制记法。
例如对于:10000000 00001011 00000011 00011111。
点分十进制表示为:128.11.3.31
3.1数据存储优先顺序的转换
计算机数据存储有两种字节优先顺序:高位字节优先(称为大端模式)和低位字节优先(称为小端模式)。
(1)内存的低地址存储数据的低字节,高地址存储数据的高字节的方式叫小端模式。
(2)内存的高地址存储数据的低字节,低地址存储数据高字节的方式称为大端模式。
3.2字节序的基本概念
如果称某个系统所采用的字节序为主机字节序,则它可能是小端模式的,也可能是大端模式的。而端口号和IP地址都是以网络字节序存储的,不是主机字节序,网络字节序都是大端模式。
要把主机字节序和网络字节序相互对应起来,需要对这两个字节存储优先顺序进行相互转化。
这里用到四个函数:htons(),ntohs(),htonl()和ntohl().这四个地址分别实现网络字节序和主机字节序的转化,这里的h代表host,n代表network,s代表short,l代表long。通常16位的IP端口号用s代表,而IP地址用l来代表。
eg:对于内存中存放的数0x12345678来说
如果是采用大端模式存放的,则其真实的数是:0x12345678
如果是采用小端模式存放的,则其真实的数是:0x78563412
4.1socket服务器开发的基本步骤
(1)创建套接字。 //socket()
(2)为创建的套接字添加信息(IP地址和端口号)。//bind()
(3)监听网络连接。//listen()
(4)监听到有客户端接入,接受一个连接。//accept()
(5)数据交互。//read();write()
(6)关闭套接字,断开连接。//close()
4.2socket客户端开发的基本步骤
(1)创建套接字。 //socket()
(2)连接服务器的通信。//accept();
(3)接受服务器套接字的信息。//recv();
(4)发送服务器套接字的信息。//send();
(5)关闭套接字,断开连接。//close();
4.3相关函数原型及应用
(1)socket()函数
(1)流式socket(SOCK_STREAM) à用于TCP通信
流式套接字提供可靠的、面向连接的通信流;它使用TCP协议,从而保证了数据传输的正确性和顺序性。
(2)数据报socket(SOCK_DGRAM) à用于UDP通信
数据报套接字定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保证是可靠、无差错的。它使用数据报协议UDP。
(3)原始socket (SOCK_RAW) à用于新的网络协议实现的测试等
原始套接字允许对底层协议如IP或ICMP进行直接访问,它功能强大但使用较为不便,主要用于一些协议的开发。
(2)bind()函数
(3)listen()函数
(4)accept()函数
(5)connect()函数
(6)recv()函数
(7)send()函数
第一步:服务端的构建
#include
#include
#include
#include
#include
#include
#include
#include
int main()
{
/* 写出步骤
1.soclet()创建套接口
2.bind()绑定
3.listen()监听
4.accept()接受
5.read()读取
6.write()写入
*/
int s_fd;//定义套接口返回值
char readBuf[128];//你要输入的文字字符串长度
int n_read;
char *message = "I get your message";
struct sockaddr_in s_addr;
struct sockaddr_in c_addr;
//定义结构体变量
//这个函数在socket中多用于清空数组.如:原型是memset(buffer, 0, sizeof(buffer))
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 error\n");//打印调试信息
exit(-1);
}
s_addr.sin_family = AF_INET;//网络协议设置为IPV4
s_addr.sin_port = htons(8989);//端口号定为8989
inet_aton("127.0.0.1",&s_addr.sin_addr);//IP地址定为127.0.0.1
bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));//绑定对应的套接口描述符
listen(s_fd,10);//接受服务器数量为10
int clen = sizeof(struct sockaddr_in);
int c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&clen);//判断是否完成
if(c_fd == -1){//如果失败返回-1
perror("accpet");
}
printf("get connet:%s\n",inet_ntoa(c_addr.sin_addr));//打印连接的端口号
n_read = read(c_fd,readBuf,128);//读取客户端的数据
if(n_read == -1){//读取失败
perror("read error\n");
}else{//读取成功
printf("get message:%d,%s\n",n_read,readBuf);
}
write (c_fd,message,strlen(message));
return 0;
}
(1)运行服务器代码,等待连接
(2)在liunx里面再打开一个终端,并连入刚才的服务器。
(3)连接成功,输入一段简单的文字
此时你的服务器就会收到你输入的信息,并打印出来。
第二步:客户端的构建
#include
#include
#include
#include
#include
#include
#include
#include
int main()
{
/*1.soclet()创建套接口
2.connect()连接
3.write()写入
4.read()读取
*/
int c_fd;//定义套接口返回值
char readBuf[128];
int n_read;
char *message = "message form client\n";
struct sockaddr_in c_addr;
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(8989);
inet_aton("127.0.0.1",&c_addr.sin_addr);
if(connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr)) == -1){
perror("connect");
exit(-1);
}
write(c_fd,message,strlen(message));
n_read = read(c_fd,readBuf,128);
if(n_read == -1){
perror("read");
}else{
printf("get message form sever:%d,%s\n",n_read,readBuf);
}
return 0;
}
服务器与客户端之间的连接
运行服务器
运行客户端
客户端与服务器自动连接
第三步:客户端与服务器之间的信息交互
之前其实就已经完成了交互,但是不能够连续的交互,我们只需要添加进程和循环,让服务器和客户端一直保持联系就行
优化服务端:
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc,char **argv)//在运行时候直接输入对应的IP地址和端口号
{
/*1.soclet()创建套接口
2.bind()绑定
3.listen()监听
4.accept()接受
5.read()读取
6.write()写入
*/
int s_fd;//定义套接口返回值
char readBuf[128];
int n_read;
char message[128] = {0};
// char *message = "I get your message";
struct sockaddr_in s_addr;
struct sockaddr_in c_addr;
if(argc != 3)//提示输入端口号和IP地址
{
printf("请输入端口\n");
exit(-1);
}
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]));
inet_aton(argv[1],&s_addr.sin_addr);
bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
listen(s_fd,10);//监听数量为10
int clen = sizeof(struct sockaddr_in);
while(1){
int c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&clen);//判断是否完成三次握手如果完成就返回c_fd
if(c_fd == -1){
perror("accpet");
}
printf("get connet:%s\n",inet_ntoa(c_addr.sin_addr));
//如果创建子进程成功
if(fork() == 0){
if(fork() == 0){
//收到客户端的消息就打印,一直循环
while(1){
memset(message,0,sizeof(message));
printf("input:\n");
gets(message);
write (c_fd,message,strlen(message));
}
}
memset(readBuf,0,sizeof(readBuf));
n_read = read(c_fd,readBuf,128);
if(n_read == -1){
perror("read");
}else{
printf("get message:%d,%s\n",n_read,readBuf);
}
break;
}
}
return 0;
}
优化客户端:
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc,char **argv)
{
/*1.soclet()创建套接口
2.connect()连接
3.write()写入
4.read()读取
*/
int c_fd;//定义套接口返回值
char readBuf[128];
int n_read;
char message[128]={0};
// char *message = "message form client\n";
struct sockaddr_in c_addr;
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]));
inet_aton(argv[1],&c_addr.sin_addr);
if(connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr)) == -1){
perror("connect");
exit(-1);
}
while(1){
if(fork() == 0)
{
while(1){
memset(message,0,sizeof(message));
printf("input:\n");
gets(message);
write(c_fd,message,strlen(message));
}
}
memset(readBuf,0,sizeof(readBuf));
n_read = read(c_fd,readBuf,128);
if(n_read == -1){
perror("read");
}else{
printf("get message:%d,%s\n",n_read,readBuf);
}
}
return 0;
}
将客户端与服务端连接
将客户端连接后,服务器就会弹出对应的消息,然后在客户端和服务端之间按照提示进行输入就行
这里有一些问题,是关于进程的,就是现在我们的客户端与服务端同时创建了进程,但是服务端只有一个,我们打字过后的内容不知道会被那个进程拿走,但是我们代码是没有问题的,我们将服务器改为自动回复就行,或者使用线程的知识。
我们也可以连入多台客户端,只需要将输入对应的IP地址和端口号就行。
这一节感觉自己学的比较懵,很多函数的运用还不熟悉。