TCP协议网络编程实现两台计算机通信(详细注释)
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
3.试用C语言编写一对在以太网中发送和接收数据帧的应用,建议采用Socket 的TCP协议端口,必须提供软件设计方案和流程图,软件代码的每一行都必须加注中文注释说明设计意图,不得抄袭。
(必做题)
解答: 编程使用TCP/IP协议,采用数据流的socket套接口,TCP是面向连接的通信协议,通过三次握手建立连接,通讯完成时要拆除连接,由于TCP是面向连接的所以只能用于点对点的通讯。
对比一下,UDP是面向无连接的通讯协议,UDP数据包括目的端口号和源端口号信息,由于通讯不需要连接,所以可以实现广播发送,UDP通讯时不需要接收方确认,属于不可靠的传输,可能会出丢包现象。
程序采用服务器/客户机模式,下面是服务器的程序设计方案
【1】首先利用socket系统调用获得一个套接口
【2】系统调用bind将这个套接口绑定到主机的某个端口上
【3】端口开始侦听有无连接请求,系统调用listen
【4】没有就继续侦听,有的话执行下一步
【5】接受connect的请求,系统调用accept(),得到一个新的套接口描述符,这个时候通信管道已经完全建立好了
【6】利用这个新的描述符完成发送数据帧的操作,系统调用send()
【7】发送完毕要撤销套接口
下面是完整程序和详细注释
清单01 server.c
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<sys/wait.h> /头文件
#define MYPORT 3490 /定义用于通信的端口号
#define BACKLOG 10 /定义等待队列中最多存放的connect请求个数main() /主函数这里开始
{int sockfd,new_fd; /这两个是调用socket()系统调用得到的文件描述符struct sockaddr_in my_addr; /结构体定义,这是自己和对方的套接口的地址信息struct sockaddr_in their_addr;
int sin_size; /其实这是accept()里面的第三个参数,这里先定义if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1)
{perror("socket"); /进入正题,首先socket()系统调用,sockfd就是exit(1); /所得套接口的文件描述符,如果调用失败,perror会} /显示错误信息,然后非正常退出
my_addr.sin_family=AF_INET; / ADDRESS FAMILY 地址族
my_addr.sin_port=htons(MYPORT);/ 把整数MYPORT转换成“网络字节顺序”my_addr.sin_addr.s_addr=INADDR_ANY;/ 使用自己的IP地址, 自动填上它所运
行的机器的 IP 地址
bzero(&(my_addr.sin_zero),8);/结构体中剩下的清0
上面是第一步,调用socket()得到了一个套接口,下面将调用bind()将其绑定到本地计算机的某个端口上,当然这里就是前面定义的那个端口
if(bind(sockfd,(struct sockaddr *)&my_addr,sizeof(struct sockaddr))==-1)
{perror("bind"); /如果失败就显示bind()调用出错原因,成功的话
exit(1); /就继续往下运行
}
其实利用TCP/IP协议进行数据流的传输就像两个地方搭个管道输送水流一样,现在这里是水电站,前面两步已经弄好了一个出水口,下面就要监听对方是否需要我送水过去,运行listen()系统调用。
假如你不希望与远程的一个地址相连,那你就需要等待接入请求并且用各种方法处理它们。
处理过程分两步:首先,你听--listen(),然后,你接受--accept() (请看下面的内容)。
if (listen(sockfd,BACKLOG)==-1) /BACKLOG 是在进入队列中允许的连接数目,进入的连接是在队列中一直等待直到接受 (accept() )连接
{perror("listen"); /在错误的时候返回-1,并设置全局错误变量 errno
exit(1);
}
下面就是接受一个连接请求了
while(1)
{sin_size=sizeof(struct sockaddr_in);
if((new_fd=accept(sockfd,(struct sockaddr *)&their_addr,&sin_size))==-1)
{perror("accept"); /注意这里出现了第二个文件描述符,如果只想让一个连接进来,那么你可以使用 close() 去关闭原来的文件描述符 sockfd 来避免同一个端口更多的连接
continue; /如果没有成功接受的话就继续监听然后accept
}
printf("server:got connection from %s\n",inet_ntoa(their_addr.sin_addr));
连接上了要输出一个反馈信息:server:got connection from+客户端的ip地址
然后就把数据帧发送过去,调用send(),当然我们不用去关心这个数据帧是怎么被包装的,这些交给下层处理,fork()产生一个子进程用来发送数据if(!fork())
{if (send (new_fd,"hello,world!\n",14,0)==-1)
perror("send");
close(new_fd);
exit(0);
}
close(new_fd); 发送完毕即时的关掉套接口
while(waitpid(-1,NULL,WNOHANG)>0);
最后释放子进程的资源,防止产生僵尸进程造成资源没有释放。
}
}
至此服务器程序设计完毕,下面着手客户机的程序设计,与服务器相比客户端的
设计相对简单一些,其程序设计流程大致如下
【1】系统调用socket()获得套接口
【2】由于我是要连到远方服务器端口,所以我不必绑定到自己的机器端口上,故而有了套接口下面可以直接向远方主机发送connect连接请求
【3】如果connect失败,那就返回第二步,继续connect,直到成功
【4】连接成功后通信管道就建好了,可以调用recv()来接受数据帧
【5】最后把套接口释放掉
下面是完整程序和详细注释
清单02 user.c
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<netdb.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<sys/socket.h> /头文件
#define PORT 3490 / 端口号定义,这就是将要连接到的端口
#define MAXDA TASIZE 100 /这个参数是一次所能得到的最大字节数
下面开始主函数,注意main函数是有参数的,argc=参数的个数+1,因为函数自身算一个参数,数组argv[]用来依次存放指向参数的指针
int main(int argc,char *argv[])
{
前面都是对一些参数类型进行声明
int sockfd,numbytes;
char buf[MAXDA TASIZE];
struct hostent *he;
struct sockaddr_in their_addr;
首先检验main函数是不是给了1个参数,是一个啊得注意
if(argc != 2)
{fprintf(stderr,"usage:client hostname\n"); 如果参数不是一个就报错
exit(1); 然后非正常退出
}
然后检验参数(其实这个参数就是服务器的ip地址,参数的指针就在argv[1]里面)是否有效,调用函数gethostbyname(),它的基本原理就是得到和你主机名字相匹配的ip地址。
if((he=gethostbyname(argv[1]))==NULL)
{herror("gethostbyname"); 出错的话返回出错信息
exit(1);
}
能运行到这一步说明参数个数和参数内容都是正确的,其实这才是真正的开始调用socket()得到套接口,sockfd为返回的套接口描述符
if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1)
{perror("socket");
exit(1);
}
下面的跟之前服务器端程序差不多,是对套接口信息初始化
their_addr.sin_family=AF_INET;
their_addr.sin_port=htons(PORT);
their_addr.sin_addr=*((struct in_addr *)he->h_addr);
bzero(&(their_addr.sin_zero), 8);
可以继续用我的那个比喻,如果服务器是水库,并且那里已经有个出口在侦听是不是有人要水流,那么我是用户,并且程序运行到这我也已经有了一个入口地址,是时候跟服务器取得联系了,一旦联系成功,立马就会建立一条管道,下面就是connect()系统调用
if(connect(sockfd,(struct sockaddr *)&their_addr,sizeof(struct sockaddr))==-1) {perror("connect");
exit(1);
}
连接成功,此时服务器的listen()监听到了我的connect,并且accept了我,然后send了数据帧给我,所以接下来我就得接受数据帧了,用到系统调用recv() if((numbytes=recv(sockfd,buf,MAXDATASIZE,0))==-1)
{perror("recv");
exit(1);
}
收到的数据存放到数组buf[]中,对于字符串数组最后一个停止位记得给加上
buf[numbytes]='\0';
把收到的信息显示出来吧
printf("received:%s",buf);
最后关闭套接口,释放端口
close(sockfd);
return 0;
}
至此完成用户程序的编写,采用linux编译环境,编译连接后,运行调试
在我的电脑上同时开两个窗口,分别模拟服务器和用户,键入
Ifconfig命令查看本机的ip地址,显示
本地环回inet地址127.0.0.1
运行./server 服务器开始运行
然后./user 127.0.0.1 用户程序运行
运行结果:
服务器端显示server:got connection from 127.0.0.1
客户端显示received:hello,world!
PS:我昨天运行的时候键入ifconfig出来好多ip地址,不止一个127.0.0.1,还有个180.109.92.243 我试过,也可以顺利通信,另外一个62.….…记不清了反正这个不行,但是今晚再次运行ifconfig只有一个127.0.0.1出现,没有180.109.92.243,键入这个参数提示说network is unreachable,这次不行了。