由于各种原因,要用到linux网络编程,于是就去找了一些资料加深一下对网络知识方面的理解。首先是网络模型:ISO模型:物理层->数据链路层->网络层->传输层->回话层->表示层->应用层。TCP/IP模型:网络接口层->网络互连层->传输层->应用层。这里我主要想理解理解tcp协议和udp协议。
UDP协议
UDP协议是建立在IP协议基础之上的,用在传输层的协议.UDP和IP协议一样是不可靠的数据报服务.UDP的头格式为:
0 16 32
---------------------------------------------------
| UDP源端口 | UDP目的端口 |
---------------------------------------------------
| UDP数据报长度 | UDP数据报校验 |
---------------------------------------------------
UDP结构在中的定义为:
struct udphdr {
u_int16_t source;
u_int16_t dest;
u_int16_t len;
u_int16_t check;
};
关于UDP协议的详细情况,请参考RFC768
7.5 TCP
TCP协议也是建立在IP协议之上的,不过TCP协议是可靠的.按照顺序发送的.TCP的数据结构比前面的结构都要复杂.
0 4 8 10 16 24 32
-------------------------------------------------------------------
| 源端口 | 目的端口 |
-------------------------------------------------------------------
| 序列号 |
------------------------------------------------------------------
| 确认号 |
------------------------------------------------------------------
| | |U|A|P|S|F| |
|首部长度| 保留 |R|C|S|Y|I| 窗口 |
| | |G|K|H|N|N| |
-----------------------------------------------------------------
| 校验和 | 紧急指针 |
-----------------------------------------------------------------
| 选项 | 填充字节 |
-----------------------------------------------------------------
TCP的结构在中定义为:
struct tcphdr
{
u_int16_t source;
u_int16_t dest;
u_int32_t seq;
u_int32_t ack_seq;
#if __BYTE_ORDER == __LITTLE_ENDIAN
u_int16_t res1:4;
u_int16_t doff:4;
u_int16_t fin:1;
u_int16_t syn:1;
u_int16_t rst:1;
u_int16_t psh:1;
u_int16_t ack:1;
u_int16_t urg:1;
u_int16_t res2:2;
#elif __BYTE_ORDER == __BIG_ENDIAN
u_int16_t doff:4;
u_int16_t res1:4;
u_int16_t res2:2;
u_int16_t urg:1;
u_int16_t ack:1;
u_int16_t psh:1;
u_int16_t rst:1;
u_int16_t syn:1;
u_int16_t fin:1;
#endif
u_int16_t window;
u_int16_t check;
u_int16_t urg_prt;
};
source发送TCP数据的源端口
dest接受TCP数据的目的端口
seq标识该TCP所包含的数据字节的开始序列号
ack_seq确认序列号,表示接受方下一次接受的数据序列号.
doff数据首部长度.和IP协议一样,以4字节为单位.一般的时候为5
urg如果设置紧急数据指针,则该位为1
ack如果确认号正确,那么为1
psh如果设置为1,那么接收方收到数据后,立即交给上一层程序
rst为1的时候,表示请求重新连接
syn为1的时候,表示请求建立连接
fin为1的时候,表示亲戚关闭连接
window窗口,告诉接收者可以接收的大小
check对TCP数据进行较核
urg_ptr如果urg=1,那么指出紧急数据对于历史数据开始的序列号的偏移值
关于TCP协议的详细情况,请查看RFC793
IP数据报首部的固定部分中的各字段
1. 版本:占4位
2. 首部长度:占4位
3. 服务:占8位
4. 总长度:占16位
5. 标识:16位
6. 标志:3位
7. 片偏移:占13位
8. 生存时间:占8位
9. 协议:占8位
10. 首部检验和:占16位
11. 源地址:占32位
12. 目的地址:占32位
IP数据包指的是第三层的PDU
IP首部只是其中的一部分,是在第三层上加上去的,是给路由器看到的。
IP数据包的总长度过大,超过链路的最大MTU时,数据包就会被分成多片。
对端根据IP首部中的标示符,标志,段偏置值字段重组数据包。
TCP连接的建立
TCP协议是一种可靠的连接,为了保证连接的可靠性,TCP的连接要分为几个步骤.我们把这个连接过程称为"三次握手".
下面我们从一个实例来分析建立连接的过程.
第一步客户机向服务器发送一个TCP数据包,表示请求建立连接. 为此,客户端将数据包的SYN位设置为1,
并且设置序列号seq=1000(我们假设为1000).
第二步服务器收到了数据包,并从SYN位为1知道这是一个建立请求的连接.于是服务器也向客户端发送一个TCP数据包.
因为是响应客户机的请求, 于是服务器设置ACK为1,sak_seq=1001(1000+1)同时设置自己的序列号.seq=2000(我们假设为2000).
第三步客户机收到了服务器的TCP,并从ACK为1和ack_seq=1001知道是从服务器来的确认信息.于是客户机也向服务器发送确认信息.
客户机设置ACK=1,和ack_seq=2001,seq=1001,发送给服务器.至此客户端完成连接.
最后一步服务器受到确认信息,也完成连接.
通过上面几个步骤,一个TCP连接就建立了.当然在建立过程中可能出现错误,不过TCP协议可以保证自己去处理错误的.
由于小弟我好就没编程了,练练手感,不然都忘记了。一写错,误就出来了,唉,生疏了很多。下面是小弟写的面向TCP连接的客户端和服务器段代码(由于本人很懒,就写了个很简单的,不能处理并发,其实就是创建一个线程池(也可以不创建,就是每次连接上来的时候再去创建线程,创建线程池只是维护一起来容易些,性能提高些,我是这么理解的,如果有错误,欢迎高手指正),每次检测到一个连接上来的时候就从线程池里面拿个线程去做你要想做的处理,用完之后返回给线程池。当然那,用进程处理更没有错那,只是浪费资源而已:
服务器端代码:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define portnumber 3333
int main(void)
{
int sockfd,newfd;
int sin_size;
int nbytes;
char a[1024];
memset(a,'0',1024);
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
if((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1)
{
fprintf(stderr,"socket error:%s\n\a",strerror(errno));
exit(-1);
}
bzero(&server_addr,sizeof(struct sockaddr_in));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(portnumber);
unsigned int value;
value = 1;
setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&value,sizeof(value));//这篇文章详细的阐述了为什么http://www.ibm.com/developerworks/cn/linux/l-sockpit/
if(bind(sockfd,(struct sockaddr *)(&server_addr),sizeof(struct sockaddr)) == -1)
{
fprintf(stderr,"bind error:%s\n\a",strerror(errno));
exit(-1);
}
if(listen(sockfd,5) == -1)
{
fprintf(stderr,"listen error:%s\n\a",strerror(errno));
exit(-1);
}
sin_size = sizeof(struct sockaddr_in);
while(1)
{
if((newfd = accept(sockfd,(struct sockaddr *)&client_addr,&sin_size)) == -1)
{
fprintf(stderr,"accept error:%s\n\a",strerror(errno));
exit(-1);
}
fprintf(stderr,"Server get connection from %s\n",inet_ntoa(client_addr.sin_addr));
if((nbytes = read(newfd,a,1024)) == -1)
{
fprintf(stderr,"read error:%s\n\a",strerror(errno));
exit(-1);
}
// printf("fdfdfdfd%d\n",nbytes);
a[nbytes] = '\0';
printf("client speak %s\n",a);
close(newfd);
}
close(sockfd);
exit(0);
}
客户端代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#include <netdb.h>
#define portnumber 3333
int main(int argc,char **argv)
{
int sockfd,newfd;
struct sockaddr_in server_addr;
struct hostent *host;
char *a = "hello server!!!!!!";
if(argc != 2)
{
fprintf(stderr,"Usage:%s hostname \a\n",argv[0]);
exit(-1);
}
if((host = gethostbyname(argv[1])) == NULL)
{
fprintf(stderr,"gethostbyname error\n");
exit(-1);
}
if((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1)
{
fprintf(stderr,"socket error:%s\n\a",strerror(errno));
exit(-1);
}
bzero(&server_addr,sizeof(struct sockaddr_in));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(portnumber);
server_addr.sin_addr = *((struct in_addr *)host->h_addr);
if(connect(sockfd,(struct sockaddr *)&server_addr,sizeof(struct sockaddr))== -1)
{
fprintf(stderr,"Connect error:%s\n\a",strerror(errno));
exit(-1);
}
if(write(sockfd,a,strlen(a)) == -1)
{
fprintf(stderr,"read error:%s\n",strerror(errno));
exit(-1);
}
// printf("%s",a);
close(sockfd);
return 0;
}
面向UDP连接的程序代码(这个其实你可以看成一个简单的聊天程序,不带GUI而已哈,呵呵):
服务器端:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <netinet/in.h>
#include <strings.h>
#include <string.h>
#define portnumber 3333
int main(void)
{
int sockfd;
char recevbuf[50];
int recenum,sendnum,client_len;
recenum = 0;
sendnum = 0;
memset(recevbuf,'a',50);
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
if((sockfd = socket(AF_INET,SOCK_DGRAM,0)) == -1)
{
fprintf(stderr,"socket error:%s\n\a",strerror(errno));
exit(-1);
}
client_len = sizeof(struct sockaddr);
bzero(&server_addr,sizeof(struct sockaddr_in));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(portnumber);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(sockfd,(struct sockaddr *)&server_addr,sizeof(struct sockaddr))== -1)
{
fprintf(stderr,"bind error:%s\n\a",strerror(errno));
return 0;
}
while(1)
{
if((recenum = (recvfrom(sockfd,recevbuf,1024,0,(struct sockaddr *)&client_addr,&client_len))) == -1)
{
fprintf(stderr,"recefrom error:%s\n\a",strerror(errno));
exit(-1);
}
else{
recevbuf[recenum] = '\0';
printf("server accept the string:%s\n",recevbuf);
}
if(fgets(recevbuf,50,stdin) == NULL)
{
perror("fgets:");
exit(-1);
}
if( (sendnum = sendto(sockfd,recevbuf,recenum,0,(struct sockaddr *)&client_addr,client_len)) == -1)
{
fprintf(stderr,"sendto error:%s\n\a",strerror(errno));
exit(-1);
}
// printf("%d",sendnum);
}
close(sockfd);
return 0;
}
客户端:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <strings.h>
#define portnumber 3333
int main(int argc,char **argv)
{
int sockfd;
struct sockaddr_in server_addr;
int client_len;
int send_num;
send_num = 0;
client_len = 0;
char send_buf[50];
char recv_buf[50];
memset(send_buf,'0',sizeof(send_buf));
memset(recv_buf,'0',sizeof(recv_buf));
sockfd = socket(AF_INET,SOCK_DGRAM,0);
bzero(&server_addr,sizeof(struct sockaddr_in));
if(argc !=2)
{
fprintf(stderr,"Usage: %s host",argv[0]);
exit(-1);
}
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(portnumber);
server_addr.sin_addr.s_addr = inet_addr(argv[1]);
client_len = sizeof(struct sockaddr);
if(connect(sockfd,(struct sockaddr *)&server_addr,client_len) == -1)
{
fprintf(stderr,"Connect error:%s\n\a",strerror(errno));
exit(-1);
}
while(1)
{
if(fgets(send_buf,50,stdin) == NULL)
{
perror("fgets");
exit(-1);
}
if((send_num = sendto(sockfd,send_buf,strlen(send_buf),0,(struct sockaddr *)&server_addr,client_len)) == -1)
{
fprintf(stderr,"send_to error:%s\n\a",strerror(errno));
exit(-1);
}
if((send_num = recvfrom(sockfd,recv_buf,sizeof(recv_buf),0,(struct sockaddr *)&server_addr,&client_len)) == -1)
{
fprintf(stderr,"recvfrom error:%s\n\a",strerror(errno));
exit(-1);
}
else{
recv_buf[send_num] = '\0';
printf("client accept the string:%s\n",recv_buf);
}
}
close(sockfd);
return 0;
}
在这次的编程实践中,主要一下几点在以后的编程中一定要注意。
第一点:就是一定要给调用的函数加入错误处理。也许程序运行正确的时候这部分代码没什么用,可是一旦出现出错,排查就比较困难,特别是对那种架构比较庞大的程序。
第二点:一个关于C语言语法的问题,唉,本人菜鸟没办法,到现在还出现C语言语法错误。对字符串的理解,C语言中是没有字符串的。对于要用到的地方只能字符数组来代替。也就是说要用到字符串的地方可以用字符数组来代替。一定要注意的是一定要保证这个字符数组最后一个字符为‘\0’,因为这样才是一个字符串。没有‘\0’,当你调用像printf()函数,或者strcpy函数时会出现未知错误(行为不确定)。因为这些函数在处理传入的字符串时是以字符‘\0’当成结束符的。
第三点:多学会用man。
第四点:坚持加自信。多写总结,不然学到的东西果断时间不用的话也都忘记了。
最后向大家推荐个网站:http://www.ibm.com里面很多很优秀的论文,很有指导意义。