(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
2. #include
3. #include
4. #include
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 \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
2. #include
3. #include
4. #include
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 \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的原因以及相关代码,通过分析,发现是如下语句会提示错误:
ErrorHandling("connect() error!");
通过分析,connect()函数中的hSocket套接字没有赋值。
(3)错误改正与总结:
在main()函数下为hsocket赋值,语句如下:
根据数据传输方式的不同,基于网络协议的套接字一般分成TCP套接字和UDP套接字。
TCP套接字是面向连接的,又称为基于流Stream的套接字,TCP,TCP/IP协议栈分成4层架构。不同于OSI 7层架构:物理层–>数据链路层–>网络层–>传输层–>会话层–>表示层–>应用层。实验实习的是TCP实现基于TCP的服务器端和客户端: