首页 > 编程 > C++ > 正文

用C语言进行最基本的socket编程

2020-05-23 14:12:50
字体:大 中 小
来源:转载
供稿:网友

这篇文章主要介绍了C语言下socket编程的基本知识讲解,包括最基本的客户端发送及服务器端接受数据的实现,需要的朋友可以参考下

什么是socket

你经常听到人们谈论着 “socket”,或许你还不知道它的确切含义。现在让我告诉你:它是使用 标准Unix 文件描述符 (file descriptor) 和其它程序通讯的方式。什么?你也许听到一些Unix高手(hacker)这样说过:“呀,Unix中的一切就是文件!”那个家伙也许正在说到一个事实:Unix 程序在执行任何形式的 I/O 的时候,程序是在读或者写一个文件描述符。一个文件描述符只是一个和打开的文件相关联的整数。但是(注意后面的话),这个文件可能是一个网络连接,FIFO,管道,终端,磁盘上的文件或者什么其它的东西。Unix 中所有的东西就是文件!所以,你想和Internet上别的程序通讯的时候,你将要使用到文件描述符。你必须理解刚才的话。现在你脑海中或许冒出这样的念头:“那么我从哪里得到网络通讯的文件描述符呢?”,这个问题无论如何我都要回答:你利用系统调用 socket(),它返回套接字描述符 (socket descriptor),然后你再通过它来进行send() 和 recv()调用。“但是...”,你可能有很大的疑惑,“如果它是个文件描述符,那么为什 么不用一般调用read()和write()来进行套接字通讯?”简单的答案是:“你可以使用!”。详细的答案是:“你可以,但是使用send()和recv()让你更好的控制数据传输。”存在这样一个情况:在我们的世界上,有很多种套接字。有DARPA Internet 地址 (Internet 套接字),本地节点的路径名 (Unix套接字),CCITT X.25地址 (你可以将X.25 套接字完全忽略)。也许在你的Unix 机器上还有其它的。我们在这里只讲第一种:Internet 套接字。

Internet 套接字的两种类型 :

什么意思?有两种类型的Internet 套接字?是的。不,我在撒谎。其实还有很多,但是我可不想吓着你。我们这里只讲两种。除了这些, 我打算另外介绍的 "Raw Sockets" 也是非常强大的,很值得查阅。

那么这两种类型是什么呢?一种是"Stream Sockets"(流格式),另外一种是"Datagram Sockets"(数据包格式)。我们以后谈到它们的时候也会用到"SOCK_STREAM" 和 "SOCK_DGRAM"。数据报套接字有时也叫“无连接套接字”(如果你确实要连接的时候可以用connect()。) 流式套接字是可靠的双向通讯的数据流。如果你向套接字按顺序输出“1,2”,那么它们将按顺序“1,2”到达另一边。它们是无错误的传递的,有自己的错误控制,在此不讨论。

有什么在使用流式套接字?你可能听说过 telnet,不是吗?它就使用流式套接字。你需要你所输入的字符按顺序到达,不是吗?同样,WWW浏览器使用的 HTTP 协议也使用它们来下载页面。实际上,当你通过端口80 telnet 到一个 WWW 站点,然后输入 “GET pagename” 的时候,你也可以得到 HTML 的内容。为什么流式套接字可以达到高质量的数据传输?这是因为它使用了“传输控制协议 (The Transmission Control Protocol)”,也叫 “TCP” (请参考 RFC-793 获得详细资料。)TCP 控制你的数据按顺序到达并且没有错

误。你也许听到 “TCP” 是因为听到过 “TCP/IP”。这里的 IP 是指“Internet 协议”(请参考 RFC-791。) IP 只是处理Internet 路由而已。

那么数据报套接字呢?为什么它叫无连接呢?为什么它是不可靠的呢?有这样的一些事实:如果你发送一个数据报,它可能会到达,它可能次序颠倒了。如果它到达,那么在这个包的内部是无错误的。数据报也使用 IP 作路由,但是它不使用 TCP。它使用“用户数据报协议 (User Datagram Protocol)”,也叫 “UDP” (请参考 RFC-768。)

为什么它们是无连接的呢?主要是因为它并不象流式套接字那样维持一个连接。你只要建立一个包,构造一个有目标信息的IP 头,然后发出去。无需连接。它们通常使用于传输包-包信息。简单的应用程序有:tftp, bootp等等。

你也许会想:“假如数据丢失了这些程序如何正常工作?”我的朋友,每个程序在 UDP 上有自己的协议。例如,tftp 协议每发出的一个被接受到包,收到者必须发回一个包来说“我收到了!” (一个“命令正确应答”也叫“ACK” 包)。如果在一定时间内(例如5秒),发送方没有收到应答,它将重新发送,直到得到 ACK。这一ACK过程在实现SOCK_DGRAM 应用程序的时候非常重要。

简单的发送和接收实现

服务器端接收代码:

 

 
  1. #include <Winsock2.h> 
  2. #pragma comment(lib,"Ws2_32.lib") 
  3. #include <stdio.h> 
  4. #include <memory.h> 
  5.  
  6. void main() 
  7. { 
  8. WSAData wsd; 
  9. WSAStartup(MAKEWORD(2,0),&wsd); 
  10.  
  11. SOCKET s =NULL; 
  12. s=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); 
  13. struct sockaddr_in ch; 
  14. memset(&ch,0,sizeof(ch)); 
  15. ch.sin_family=AF_INET; 
  16. ch.sin_addr.s_addr=INADDR_ANY; 
  17. ch.sin_port=htons(1041); 
  18. int b=bind(s,(struct sockaddr *) &ch,sizeof(ch)); 
  19. #define QUEUE_SIZE 5 
  20. int l=listen(s,QUEUE_SIZE); 
  21. printf("正在监听本机的1041端口!/n"); 
  22. SOCKET sc=accept(s,0,0); 
  23. printf("客户端已经连接到本机的1041端口!/n"); 
  24. #define BUF_SIZE 4096 
  25. int receByt=0; 
  26. while(1) 
  27. { 
  28. char buf[BUF_SIZE]; 
  29. receByt=recv(sc,buf,BUF_SIZE,0); 
  30. buf[receByt]='/0'; 
  31. if(receByt>0) 
  32. { 
  33. printf("接收的消息是:%s/n",buf); 
  34. } 
  35. else 
  36. { 
  37. printf("接收消息结束!"); 
  38. break; 
  39. } 
  40.  
  41. } 
  42. int ic=closesocket(sc); 
  43. int is=closesocket(s); 
  44.  
  45. } 

客户端发送的代码:

 

 
  1. #include <Winsock2.h> 
  2. #pragma comment(lib,"Ws2_32.lib") 
  3. #include <stdio.h> 
  4. #include <memory.h> 
  5. #include <string.h> 
  6.  
  7. void main() 
  8. { 
  9. WSAData wsd; 
  10. WSAStartup(MAKEWORD(2,0),&wsd); 
  11.  
  12. SOCKET s =NULL; 
  13. s=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); 
  14. struct sockaddr_in ch; 
  15. memset(&ch,0,sizeof(ch)); 
  16. ch.sin_family=AF_INET; 
  17. ch.sin_addr.s_addr=inet_addr("127.0.0.1"); 
  18. ch.sin_port=htons(1041); 
  19.  
  20. int c=connect(s,(struct sockaddr *) &ch,sizeof(ch)); 
  21. printf("已经连接到服务器的1041端口!现在可以向服务器发送消息了!/n"); 
  22. #define BUF_SIZE 4096 
  23. char info[1024],buf[BUF_SIZE]; 
  24.  
  25. while(1) 
  26. { 
  27. gets(info); 
  28. if(info[0]=='/0') 
  29. break; 
  30. strcpy(buf,info); 
  31. int nsend=send(s,buf,strlen(buf),0); 
  32.  
  33. } 
  34. int ic=closesocket(s); 
  35. } 

程序代码经过了优化,并且整合多线程,把接收和发送放到同一个文件中,使用参数模式调用发送和接收模块。增加了创建SOCKET的创建的时候s句柄(或对象)判断返回值是否为INVALID_SOCKET,以及socket的bind操作的返回值是否为SOCKET_ERROR,其他socket的操作应该也判断SOCKET_ERROR,以保证程序的稳定性,这里只是测试代码就不去写这么多了,剩下的就由你个人发挥。

 

 
  1. #include <Winsock2.h> 
  2. #pragma comment(lib,"Ws2_32.lib") 
  3. #include <stdio.h> 
  4. #include <memory.h> 
  5. #include <string.h> 
  6. #include <pthread.h> 
  7.  
  8.  
  9. void Receive(); 
  10. void Send(); 
  11. void creatThread(); 
  12.  
  13. SOCKET s =NULL; 
  14. pthread_t t[1000]; 
  15. int threadCount=0; 
  16.  
  17. void main(int argc,char* argv[]) 
  18. { 
  19. printf("本程序制作人学号:713901040041/n"); 
  20. printf("程序说明:服务器端和客户端为同一个程序,请使用不同的参数运行./n"); 
  21. printf("接收程序请使用 r参数;发送程序请使用 s参数。/n"); 
  22. //printf("len : %d/n", argc); 
  23. //printf("count %d/n",argc); 
  24. //printf("value: %s/n",argv[1]); 
  25. //printf("%d",argv[1][0]=='r'); 
  26.  
  27. if(argc<=1) 
  28. { 
  29. printf("please input program arguments .../n"); 
  30. exit(0); 
  31. } 
  32. if(argc>1 && argv[1][0]=='r') 
  33. { 
  34. printf("run receive .../n"); 
  35. Receive(); 
  36. } 
  37. if(argc>1 && argv[1][0]=='s') 
  38. { 
  39. printf("run send .../n"); 
  40. Send(); 
  41. } 
  42. } 
  43.  
  44.  
  45. void* receiveWork(void * args) 
  46. { 
  47. SOCKET sc=accept(s,0,0); 
  48. if(sc==INVALID_SOCKET) 
  49. { 
  50. printf("sc Error"); 
  51. } 
  52. creatThread(); 
  53.  
  54. printf("----------客户端已经连接到本机的%d线程连接!/n",threadCount-2); 
  55. #define BUF_SIZE 4096 
  56. int receByt=0; 
  57. while(1) 
  58. { 
  59. char buf[BUF_SIZE]; 
  60. receByt=recv(sc,buf,BUF_SIZE,0); 
  61. buf[receByt]='/0'; 
  62. if(receByt>0) 
  63. { 
  64. printf("线程接收的消息是:%s/n",buf); 
  65. } 
  66. else 
  67. { 
  68. printf("客户端已退出,"); 
  69. break; 
  70. } 
  71.  
  72. } 
  73. int ic=closesocket(sc); 
  74. printf("服务器结束连接!/n"); 
  75. return NULL; 
  76. } 
  77.  
  78. void creatThread() 
  79. { 
  80. pthread_create(&t[threadCount++],NULL,receiveWork,NULL); 
  81. } 
  82.  
  83.  
  84. void Receive() 
  85. { 
  86. WSAData wsd; 
  87. WSAStartup(MAKEWORD(2,0),&wsd);  
  88. s=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); 
  89. if(s==INVALID_SOCKET) 
  90. { 
  91. printf("socket created Error"); 
  92. } 
  93. struct sockaddr_in ch; 
  94. memset(&ch,0,sizeof(ch)); 
  95. ch.sin_family=AF_INET; 
  96. ch.sin_addr.s_addr=INADDR_ANY; 
  97. ch.sin_port=htons(1041); 
  98. int b=bind(s,(struct sockaddr *) &ch,sizeof(ch)); 
  99. if(b==SOCKET_ERROR) 
  100. { 
  101. printf("bind 失败,出错代码是:%d/n",WSAGetLastError()); 
  102. exit(0); 
  103. } 
  104. #define QUEUE_SIZE 5 
  105. int l=listen(s,QUEUE_SIZE); 
  106. printf("正在监听本机的1041端口!/n"); 
  107.  
  108. creatThread(); 
  109.  
  110. for(int i=0;i<1000;i++) 
  111. { 
  112. pthread_join(t[i],NULL); 
  113. } 
  114.  
  115. int is=closesocket(s); 
  116. } 
  117.  
  118.  
  119.  
  120. void Send() 
  121. { 
  122. WSAData wsd; 
  123. WSAStartup(MAKEWORD(2,0),&wsd); 
  124.  
  125. SOCKET s =NULL; 
  126. s=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); 
  127. if(s==INVALID_SOCKET) 
  128. { 
  129. printf("socket created Error"); 
  130. } 
  131. struct sockaddr_in ch; 
  132. memset(&ch,0,sizeof(ch)); 
  133. ch.sin_family=AF_INET; 
  134. ch.sin_addr.s_addr=inet_addr("127.0.0.1"); 
  135. ch.sin_port=htons(1041); 
  136.  
  137. int c=connect(s,(struct sockaddr *) &ch,sizeof(ch)); 
  138. printf("已经连接到服务器的1041端口!现在可以向服务器发送消息了!/n"); 
  139. #define BUF_SIZE 4096 
  140. char info[1024],buf[BUF_SIZE]; 
  141.  
  142. while(1) 
  143. { 
  144. gets(info); 
  145. if(info[0]=='/0') 
  146. break; 
  147. strcpy(buf,info); 
  148. int nsend=send(s,buf,strlen(buf),0); 
  149. } 
  150. int ic=closesocket(s); 
  151. } 


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表