实验一 基于TCP和UDP的客户端和服务器端

一、 实验要求

(1)分别编写基于TCP和UDP的Windows和Linux程序客户端和服务器端;
(2)实现TCP客户端和服务器端之间的基本的数据收发;
(3)实现UDP客户端和服务器端之间的基本的数据收发;

二、实验目的

(1)掌握基于C语言的Socket编程相关函数和数据类型;
(2)掌握WIN32和Linux操作系统下的程序的基本编程方法,以及TCP、UDP编程的基本方法;
(3)熟练掌握UDP、TCP 客户端/服务器端模式的通信原理,及编程命令;

三、 实验环境

Windows 2007,DEV C++。

四、 实验内容

一个简单的客户机/服务器程序的实现。基本原理:
服务器端:
(1)调用socket函数创建套接字;
(2)调用bind函数绑定socket和端口号;
(3)调用listen函数监听连接请求;
(4)调用accept函数接收来自客户端的连接请求;
(5)调用send()、recv()函数和read()、write()函数进行数据的传输;
(6)调用close()函数关闭套接字;
客户端:
(1)调用socket()函数创建套接字;
(2)调用connect()函数连接指定服务器的端口;
(3)调用send()、recv()函数和read()、write()函数进行数据的传输;
(4)调用close()函数关闭套接字;

五、 实验原代码

(1)服务端代码

1.	#include<stdio.h>  
2.	#include<stdlib.h>  
3.	#include<string.h>  
4.	#include<winsock2.h>  
5.	  
6.	#define BUF_SIZE 1024  
7.	void ErrorHandling(char *message);  
8.	  
9.	int main(int argc,char *argv[])  
10.	{  
11.	    WSADATA wsaData;//定义数据类型   
12.	    SOCKET hServSock,hClntSock;  
13.	    char message[BUF_SIZE];//消息数组   
14.	    int strLen,i;  
15.	    SOCKADDR_IN servAdr,clntAdr;//指明地址信息   
16.	    int clntAdrSize;  
17.	//初始化个变量,以及定义结构体      
18.	    if(argc!=2)//输入两个值   
19.	    {  
20.	        printf("Usage : %s <port>\n", argv[0]);  
21.	        exit(1);  
22.	    }  
23.	      
24.	    if(WSAStartup(MAKEWORD(2,2),&wsaData)!=0)  
25.	/*windows环境下的winsock初始化,成功返回0,失败返回非0的错误代码, 
26.	第一个参数表示程序员需要用到的winsock版本 ,第二个表示WSADATA结构体变量的地址*/   
27.	       ErrorHandling("WSAStartup() error!");//代码出错   
28.	      
29.	    hServSock=socket(PF_INET,SOCK_STREAM,0);//调用socket函数创建套接字   
30.	    if(hServSock==INVALID_SOCKET)//socket创建的套接字为无效套接字   
31.	        ErrorHandling("socket() error");  
32.	      
33.	    memset(&servAdr,0,sizeof(servAdr));//初始化地址结构体信息   
34.	    servAdr.sin_family=AF_INET;//sin_family指代协议族,在socket编程中只能是AF_INET   
35.	    servAdr.sin_addr.s_addr=htonl(INADDR_ANY);//sin_addr存储IP地址,使用in_addr这个数据结构   
36.	    //htonl函数将一个32位数从主机字节顺序转换成网络字节顺序。   
37.	    servAdr.sin_port=htons(atoi(argv[1]));//sin_port存储端口号(使用网络字节顺序)   
38.	    /*htons是将整型变量从主机字节顺序转变成网络字节顺序,  
39.	    就是整数在地址空间存储方式变为高位字节存放在内存的低地址处。 
40.	    atoi (表示 ascii to integer)是把字符串转换成整型数的一个函数*/   
41.	    if(bind(hServSock,(SOCKADDR*)&servAdr,sizeof(servAdr))==SOCKET_ERROR)  
42.	    /*绑定,即给创建好的套接字分配IP地址和端口号 */   
43.	        ErrorHandling("bind() error");  
44.	      
45.	    if(listen(hServSock,5)==SOCKET_ERROR)//监听,将套接字转换成可接收的连接状态,调用其客户端的连接   
46.	    /*listen函数进入等待连接请求状态,成功返回0,失败返回-1, 
47.	    hServSock表示进入等待的套接字文件描述符(监听套接字), 
48.	    5:表示连接请求队列长度,表示最多有5个连接请求进入队列*/   
49.	        ErrorHandling("listen() error");  
50.	      
51.	    clntAdrSize=sizeof(clntAdr);//调用5次   
52.	      
53.	    for(i=0;i<5;i++)  
54.	    {  
55.	        hClntSock=accept(hServSock,(SOCKADDR*)&clntAdr,&clntAdrSize);  
56.	        /*调用其受理客户端连接请求,成功返回套接字句柄,失败返回INVALID_SOCKET 
57.	        hServSock:套接字文件描述符, 
58.	        (SOCKADDR*)&clntAdr:保存发起连接请求的客户端地址信息的变量地址,服务端的地址信息  
59.	        &clntAdrSize:结构体的长度,函数调用完后,该变量呗填入客户端地址长度*/  
60.	        if(hClntSock==-1)  
61.	           ErrorHandling("accept() error");  
62.	        else  
63.	            printf("Connected client %d \n",i+1);  
64.	          
65.	    while((strLen=recv(hClntSock,message,BUF_SIZE,0))!=0)  
66.	    /*接收数据,成功时返回套接字的字节数(收到EOF时为0),失败时返回SOCKET_ERROR。 
67.	    第一个参数表示数据接收对象连接的套接字句柄,2:表示保存数据的缓冲区地址; 
68.	    3:表示能够接收的最大字节序;第4个参数表示接收数据时用到的多种选项信息*/   
69.	        send(hClntSock,message,strLen,0);  
70.	    /*发送数据,成功时返回传输的字节数,失败时返回SOCKET_ERROR*/      
71.	    closesocket(hClntSock);  
72.	    }  
73.	    closesocket(hServSock);//关闭套接字   
74.	    WSACleanup();//int WSACleanup(void)成功时返回0,失败返回SOCK_ERROR。注销该库   
75.	    return 0;  
76.	}  
77.	  
78.	void ErrorHandling(char *message)  
79.	{  
80.	    fputs(message,stderr);  
81.	    fputc('\n',stderr);  
82.	    exit(1);      
83.	}  
84.	

(2)客户端

1.	#include<stdio.h>  
2.	#include<stdlib.h>  
3.	#include<string.h>  
4.	#include<winsock2.h>  
5.	  
6.	#define BUF_SIZE 1024  
7.	void ErrorHandling(char *message);  
8.	  
9.	int main(int argc,char *argv[])  
10.	{  
11.	    WSADATA wsaData;  
12.	    SOCKET hSocket;  
13.	    char message[BUF_SIZE];  
14.	    int strLen;  
15.	    SOCKADDR_IN servAdr;//初始化地址信息   
16.	      
17.	    if(argc!=3){  
18.	        printf("Usage : %s <IP> <port>\n", argv[0]);  
19.	        exit(1);  
20.	    }  
21.	      
22.	    if(WSAStartup(MAKEWORD(2,2),&wsaData)!=0)  
23.	       ErrorHandling("WSAStartup() error!");  
24.	      
25.	    hSocket=socket(PF_INET,SOCK_STREAM,0);    //创建套接字 ,  
26.	  
27.	    if(hSocket==INVALID_SOCKET)  
28.	       ErrorHandling("scoket() error");  
29.	      
30.	    memset(&servAdr,0,sizeof(servAdr));//初始化地址结构体信息   
31.	    servAdr.sin_family=AF_INET;  
32.	    servAdr.sin_addr.s_addr=inet_addr(argv[1]);   
33.	    servAdr.sin_port=htons(atoi(argv[2]));  
34.	       
35.	    if(connect(hSocket,(SOCKADDR*)&servAdr,sizeof(servAdr))==SOCKET_ERROR)  
36.	        ErrorHandling("connect() error!");        
37.	    else  
38.	       puts("Connected.......");  
39.	      
40.	    while(1)  
41.	    {  
42.	        fputs("Input message(Q to qiut): ",stdout);//从终端获取一个字符串   
43.	        fgets(message,BUF_SIZE,stdin);  
44.	        if(!strcmp(message,"q\n")|| !strcmp(message,"Q\n"))  
45.	           break;  
46.	          
47.	        send(hSocket,message,strlen(message),0);  
48.	        strLen=recv(hSocket,message,BUF_SIZE-1,0);  
49.	        message[strLen]=0;  
50.	        printf("Message from server: %s",message);  
51.	    }  
52.	    closesocket(hSocket);  
53.	    WSACleanup();  
54.	    return 0;  
55.	 }   
56.	   
57.	void ErrorHandling(char *message)  
58.	{  
59.	    fputs(message,stderr);   
60.	    fputc('\n',stderr);  
61.	    exit(1);  
62.	}  

六、 实验结论

服务端代码编译运行结果截图:
服务端代码编译运行结果截图
第一段代码表示第一个客户连接到回声服务端,接收到服务并终止连接。
客户端代码编译运行结果截图:
客户端代码编译运行结果截图
回声服务器端/客户端以字符串为单位传输数据,服务端接收到信息后反馈给客户端。

七、 实验错误及改正

(一)错误一:实验环境
(1)错误一描述:在DEV C++环境下编译代码发现代码编译出错,通过检查发现代码并没有错误,compiler报错提示为:link环境出错,找不到套接字相关文件(以client_win.c文件为例)。

(2)错误分析:通过查阅相关资料以及与同学商量,发现是需要配置link环境,网络编程需要导入头文件winsock2.h,以及链接ws2_32.lib库,预实验我是按照上课老师演示的方法在visual C++6.0版本下更改链接库,编译成功并且通过,实验室是按照在DEV C++环境下编程,更改步骤为:tools——>Compiler Options——>General——>add(-lwsocke32)

(3)更改链接库后问题解决,编译通过。在Windows环境由于不同的软件,在开发网络编程时更改环境变量的位置与方法也不相同,Visual studio 2008版本添加链接库的步骤为:选择“配置属性”——>“输入”——>“附加依赖项”也可以通过快捷键AIT+F7打开属性页。通过配置链接库,我也明白到,套接字编程与C语言类似也有相应的链接库,不同的函数,要学会调用。

(二)错误二:编译通过,运行与题目不符合。
(1)错误二来源:代码出错,通过编译,运行结果显示套接字连接错误。

(2)错误分析:找到connect error的原因以及相关代码,通过分析,发现是如下语句会提示错误:

  1. if(connect(hSocket,(SOCKADDR*)&servAdr,sizeof(servAdr))==SOCKET_ERROR)
  2.      ErrorHandling("connect() error!"); 
    

通过分析,connect()函数中的hSocket套接字没有赋值。
(3)错误改正与总结:
在main()函数下为hsocket赋值,语句如下:

  1. hSocket=socket(PF_INET,SOCK_STREAM,0); //创建套接字
    添加该语句后编译通过。

八、 总结

根据数据传输方式的不同,基于网络协议的套接字一般分成TCP套接字和UDP套接字。
TCP套接字是面向连接的,又称为基于流Stream的套接字,TCP,TCP/IP协议栈分成4层架构。不同于OSI 7层架构:物理层–>数据链路层–>网络层–>传输层–>会话层–>表示层–>应用层。实验实习的是TCP实现基于TCP的服务器端和客户端:

  1. 链路层:是物理链接领域标准化的结果,也是最基本的领域,
    专门定义LAN,WAN,MAN等网络标准。
  2. IP层:复杂网络中,负责路径的选择。IP本身是面向消息的,不可靠的协议。
  3. TCP/UDP层:已IP层提供的路劲信息为基础完成实际的数据传输,
    因此也称为传输层。TCP比UDP复杂,TCP可以保证可靠的数据传输。
  4. 应用层:根据程序特点决定服务器端和客户端之间数据传输规则。
    tcp服务器端函数调用顺序
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章