[MID project] Daily Report from Curtis
本帖最后由 paocaka 于 2010-03-07 19:19 编辑
今天主要有两点要总结:
1.考虑编写一个PC平台上的TFTP Cilent, 其实就是练习一下网络编程,熟悉一下linux的各个socket的API,因为是基于UDP协议的,所以免去了建立连接的几个函数,其中主要要用到的API是以下几个函数:socket(), recv(), send(), close(). 在加上一个TFTP的接收算法即可。 准备明天完成。
2.今天大概分析了一下g-bios里面的tftp命令,发现里面用到了connect()函数,觉得很奇怪就跟进去看了一下,发现这个connect函数里面只是简单了初始化了一下对应于套接字描述符的套接字结构体,所以我觉得有两点要改进:
1).tftp是基于UDP的,是不需要调用connect()函数来建立连接的,所以这个程序的实现不合理,近期要找个时间fix掉。
2).linux系统调用中的connect()函数应该是关系到tcp三次握手协议的,而不是简单地初始化一下套接字,今晚要找个时间确认一下,如果真的是如此的话,那g-bios的connect()函数就要改写了。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(9)
1.纠正一下昨天的错误:基于UDP的网络编程主要是调用 socket(), recvfrom()和sendto()这三个函数;
2.connect()函数是TCP client调用的函数, 用于向server端发起连接, 而server段主要是使用listen()函数以及accept()函数。
本帖最后由 paocaka 于 2010-03-08 23:16 编辑
首先,感觉今天的工作效率不高, 不过还是基本达到了预期的效果,现在先总结一下我自己在编写TFTP Client过程中的一些心得:
1.重新找回了一些网络编程的感觉, 通过写这个小程序,让我脑子里面把整个协议栈及其socket API的架构又过了一边, 对于各种API及其作用功能也了解得比较清楚了,总之,就是对从明天开始将要编写的socket API心里有底了。
2.在使用这些API的时候,我会结合对协议栈的理解,把它们的工作过程也想象一遍, 这也结合实例,也更能加深我的整个架构的理解以及为以后编写协议栈做好准备。
3.今天虽然没碰到什么大问题,但小问题却不断,C语言真的生疏了,要加紧练练了。
第二,谈谈sendto()和recvfrom()这两个函数。这两个函数是UDP协议的API, 它们比对应的TCP的API多了两个参数,即struct sockaddr和socklen, 我当时想的是,多了这两个参数是因为UDP无连接的,所以每一个数据包都要包含这次通信的全部信息,包括在和哪个主机的哪个进程的哪次通信,所以需要sockaddr这个参数帮助。今天在帮真人调试的时候,这个答案得到了确认,真人的代码有个问题很有意思,就是能和TFTP Server传输数据,但Server每次都只回应第一个数据包,抓包分析发现,数据格式内容都是正确的,ACK及其序号也是正确的,唯一的不同就是正常的ACK包目的端口都是一个很大的随机端口,即服务器使用了一个随机端口和这次通信对话,而真人的ACK包的目的端口却都是69号, 我当时一看也懵了, 我想应该是69号才对啊,怎么反倒错了,后来才想起来,服务器只是打开69号监听端口监听客户端的请求,然后再fork()一个子进程来处理这次通信,所以那些ACK包的端口自然是一个随机端口。而真人的每次都是69号端口,则说明他发的每个数据包在服务器看来都是一次新的请求,不过因为是ACK包而不是RRQ包,因此服务器不予相应,相反,因为第一次发出去的数据没收到确认,所以在不断地重传,就造成了抓包所看到的结果了。 他的代码错误是一个该传指针的地方传了一个对象造成的,那是他的代码,我在这里就不赘述了。 这个写了这么多主要因为一是对这个很感兴趣,二是里面有些部分还是猜测的没得到验证,哪位朋友要是发现了不对的地方,希望能够指点指点。
第三,回去以后把今天的API再回顾一下,深刻理解一下每个参数的意义,准备明天实现。
因为g-bios里面的socket API和POSIX标准中的很大不一样,所以修改起来工程也很大,有些东西基本上就得要全改了,我们觉得这样做不现实,而且有些地方,g-bios中的针对性更强,效率反倒更高,所以我们暂时决定先只在接口上尽量与POSIX标准中的一致, 里面的实现部分则只能走一步算一步了。 其中也参考了kernel,但发现kernel里面的考虑的太多了,对g-bios不实用价值也不大,所以放弃kernel中的模型。
今天主要是对UDP的API进行了一些修改和封装,主要是在接口方面和POSIX兼容了一下:
1.给socket结构体增加了几个成员:
struct socket
{
int family;
int type;
int protocol;
int flag;
struct net_device *ndev;
struct list_node tx_qu, rx_qu;
struct sockaddr addr;
};
关于sockaddr这个member,我们讨论了很久还是决定留下。
像family,protocol,type主要是socket()函数传了这个参数,所以暂时先定义在这里,以后再看具体怎么用了。
flag这个变量的用处在后面会说, ndev这个变量是用来记录这个socket属于哪个网卡的,因为像6410这样的开发板上都要两个网卡(还有一WiFi的),所以我们认为应该有这样的一个设备指针。
这次还有一个改变就是在基于UDP协议的应用层协议中不再使用connect函数, 但是以前sock->addr这个成员都是在connect()函数中初始化的,所以现在得再另外去实现。 现在暂定在sendto()这个函数里面实现,刚才在socket结构体里面定义的flag成员初始化为0,在sendto()里面判断,如果是0就说明时第一次调用该函数,则初始化sock->addr这个成员,并将flag置为1。 不过这样实现也不是很合理,以后可能还要改。
今天还在arp_recv_packet()函数里对request帧的回应处理,现在只有对方请求的是自己的IP时才回应。同时在ip_layer_deliver()判断了IP地址是否匹配。
明天准备对skb队列进行修改,每个端口即进程下面挂一个skb队列而不是所有的端口共用一个队列,如果还有时间写一个获取本地端口的算法。
今天把关于socket, port和queue的关系理了一下,并改写了相应的代码。
首先,一个socket应该是和一个port一一对应的,但是两个数据结构在概念上不能混为一谈,事实上我认为socket应该是port的超集,一个port和与之对应的IP地址组成的二元组才称其为一个socket。因此我们将定义一个关于port的数据结构:port_info, 并在socket结构体里面添加这个成员,使得这个层次关系显得更加明显。
port_info结构体如下:
struct port_info
{
u16 port_num;
u8 type;
struct list_node tx_qu, rx_qu;
struct list_node node;
};
并且在struct socket中添加了一个 struct port_info *port 的成员。
随后我们为port新增了四个函数,当然不一定都和struct port_info有关,有些函数仅仅只是获得一个端口号而已。
四个函数分别如下:
u16 get_port(u8 type)
{
int ret;
u16 port;
port = 2134 + port_cnt;
port_cnt += 5;
for (; port < 65536; port += 200)
{
ret = check_port(port, type);
if (!ret)
return port;
}
return -EINVAL;
}
int check_port(u16 port, u8 type)
{
struct list_node *iter;
struct port_info *curr_port;
list_for_each(iter, &g_curr_ports)
{
curr_port = OFF2BASE(iter, struct port_info, node);
if (port == curr_port->port_num)
{
if (type == curr_port->type)
{
return -EBUSY;
}
}
}
return 0;
}
struct port_info *init_port(u16 num, u8 prot_type)
{
struct port_info *port;
port = (struct port_info *)malloc(sizeof(struct port_info));
if (NULL == port)
{
printf("%s(): error %d\n", __FUNCTION__, __LINE__);
return NULL;
}
port->port_num = num;
port->type = prot_type;
list_head_init(&port->tx_qu);
list_head_init(&port->rx_qu);
list_add_tail(&port->node, &g_curr_ports);
return port;
}
void del_port(u16 num)
{
struct list_node *iter;
struct port_info *curr_port;
list_for_each(iter, &g_udp_ports)
{
curr_port = OFF2BASE(iter, struct port_info, node);
if (num == curr_port->port_num)
list_del_node(iter);
}
}
说明一下:
port_cnt是一个全局变量, 主要是为进程分配随即端口时的一个参变量,每一次被操作后会增加一个步长,考虑到目前g-bios或者说bootloader中端口的使用量不大,所以没处理端口号溢出的情况。
get_port()是一个简单获得端口的算法,参数type是标明是哪种协议的端口比如TCP,UDP以及SCTP,下面各函数的的此参数意思也是一样的。
check_port函数是用来检查当前端口是否可用。
init_port()用来初始化port_info结构体,目前在UDP通信中,它将在socket()函数中被执行,不过我觉得把它改名为alloc_port_info()会比较好。del_port()则是逆操作,用来释放这个结构体,对应地,它也应该更名为free_port_info()好点。
今天主要就做了这些,中间好像还修修改改了些地方,都是小问题也记不清了, 明天要开始写TCP协议了,今晚要好好看看规范。
本帖最后由 paocaka 于 2010-03-13 20:30 编辑
今天把TCP/IP协议详解中TCP的几个章节通读了一遍,暂时还没写代码,只是做了一些准备工作。
1.安装并学会使用了socket这个软件,以后调试TCP的代码是要用到的。
2.书上有写概念很抽象,抓了几个TCP包对照着理解了下,主要是建立连接的三次握手和结束连接的四次握手,有两个疑问,
其一,书上说主动发起连接的一端,第一个数据报SYN,它的ISN即初始序列号是一个时间戳,在BSD上是自系统启动后每0.5秒增加64000,9.5小时后归零,但是我们实际抓的几次包,这个ISN都是0,这个有点奇怪,不过可能跟系统有关,不过我个人觉得这是不是不合理呢,因为这样的话如何去区分不同的连接请求呢?
其二,结束连接的四次握手是不是只有三个数据报呢?我实际只看到了三个,并且中间那个是一个FIN兼带ACK的数据报,我在状态转换图中也看了这样的状态,不过还是不是很确定。
3.关于DATAGRAM和STREAM的关系还是没有区别很清楚,只知道TCP是字节流,它本身不对字节流做任何解释,并且当TCP数据被分片时,TCP头中有该分片的偏移信息,这么说在peer端TCP层应该完成对分片的重新排列组合,也也就是说这整个过程对于应用层来说应该是透明的了。而基于DATAGRAM的UDP则不会做这些过程,每一个UDP包都是独立的,应用层要自己去完成组包工作。这是我的理解也不知道对不对。
今天看完了规范,觉得TCP很复杂,因为要提供可靠的服务,所以考虑的可能性太多了,能不能写好还没有十足的把握,目前要写的主要是建立连接的三次握手。
明天周末,可能也是继续看文档,构思一下架构,真正写可能是在下周一了。
DONE:
今天开始在协议栈中写tcp的函数,正在调试三次握手的过程。
TODO:
1.继续完成三次握手
2.完成关闭连接的过程。
本帖最后由 paocaka 于 2010-03-16 21:55 编辑
DONE:
今天终于能收到包了,竟然是校验和的问题。
TODO:
明天继续建立连接和关闭连接。
DONE:
1.确定了完成一次TCP连接所需的最基本的几个函数和结构体,当这些函数和结构体实现以后应该能完成建立连接和关闭连接以及最简单的未分段的数据的传输。
2.封装了上述提到过的函数中的几个,调试中出错,还没定位错误。
TODO:
1.找出错误源及更正错误。
2.完成这个基本架构。
3.如果完成了第二项,则进行测试。