热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

socket编程---简单的C/S之间的通信

一、socket通信过程简介在WIN32平台上的WINSOCK编程都要经过下列步骤:定义变量->获得WINDOCK版本->加载WINSOCK库->初始化->创

一、socket通信过程简介

在WIN32平台上的WINSOCK编程都要经过下列步骤:

定义变量->获得WINDOCK版本->加载WINSOCK库->初始化->创建套接字->设置套接字选项->关闭套接字->卸载WINSOCK库->释放资源。

在VC中进行WINSOCK编程时,需要引入如下两个库文件:WINSOCK.H(这个是WINSOCK API的头文件,WIN2K以上支持WINSOCK2,所以WINSOCK2.H);Ws2_32.lib(WINSOCK API连接库文件)。
使用方式如下:
           #include
           #pragma comment(lib,"ws2_32.lib")

二、重要函数

1.加载winsock文件

/*加载winsock文件*/
  WSADATA wsaData;                    //WSADATA结构被用来储存调用AfxSocketInit全局函数返回的Windows Sockets初始化信息    定义在Winsock.h
  WORD sockVersion=MAKEWORD(2,0);     //使用WINSOCK2版本
 
  ::WSAStartup(sockVersion,&wsaData);   //第一个参数是WINSOCK 版本号,第二个参数是指向WSADATA的指针.
                                        //该函数返回一个INT型值,通过检查这个值来确定初始化是否成功   

2.创建服务器端的套接字

/*创建服务器端的套接字*/
  SOCKET s=::socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
                                    //三个参数分别代表 使用TCP/IP;传输过程使用TCP;不适用其他特殊协议

  if(s==INVALID_SOCKET)
  {   
	  printf("Failed socket()\n");
      ::WSACleanup();
      system("pause");
	  return 0;
  }

3.socket中装入地址信息

(服务器端)

/*socket中装入地址信息*/
  sockaddr_in sin;
  sin.sin_family=AF_INET;              //sin_family指代协议族,在socket编程中只能是AF_INET
  sin.sin_port=htons(13);              //表示服务器监听的端口号为13
  sin.sin_addr.S_un.S_addr=INADDR_ANY; //存储IP地址,INADDR_ANY就是指定地址为0.0.0.0的地址,这个地址事实上表示不确定地址,或“所有地址”、“任意地址”

(客户端)

 /*socket中装入地址信息*/
  sockaddr_in servAddr;
  servAddr.sin_family=AF_INET;
  servAddr.sin_port=htons(13); /*接收服务器13端口号*/
  servAddr.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");/*本地IP地址为127.0.0.1*/

4.绑定地址及端口号

/*绑定地址及端口号*/
  if(::bind(s,(LPSOCKADDR)&sin,sizeof(sin))==SOCKET_ERROR)//返回:0---成功,-1---失败
  {   
	  printf("Failed bind()\n");
      ::WSACleanup();
      system("pause");
	  return 0;
  }

在套接口中,一个套接字只是用户程序与内核交互信息的枢纽,它自身没有太多的信息,也没有网络协议地址和端口号等信息,在进行网络通信的时候,必须把一个套接字与一个地址相关联,这个过程就是地址绑定的过程。
许多时候内核会我们自动绑定一个地址,然而有时用户可能需要自己来完成这个绑定的过程,以满足实际应用的需要,最典型的情况是一个服务器进程需要绑定一个众所周知的地址或端口以等待客户来连接。这个事由bind的函数完成。

参数一:指定与那个套接字绑定;

参数二:指定地址;

参数三:确定复制多少数据。

bind函数并不是总是需要调用的,只有用户进程想与一个具体的地址或端口相关联的时候才需要调用这个函数。如果用户进程没有这个需要,那么程序可以依赖内核的自动的选址机制来完成自动地址选择,而不需要调用bind的函数,同时也避免不必要的复杂度。
在一般情况下,对于服务器进程问题需要调用bind函数,对于客户进程则不需要调用bind函数

5.监听客户端的连接请求(服务器端调用)

/*监听客户端的连接请求*/
  if(::listen(s,2)==SOCKET_ERROR)//返回:0---成功,-1---失败
  {   
	  printf("Failed listen()\n");
      ::WSACleanup();
      system("pause");
	  return 0;
  }

listen函数使主动连接套接口变为被连接套接口,使得一个进程可以接受其它进程的请求,从而成为一个服务器进程。在TCP服务器编程中listen函数把进程变为一个服务器,并指定相应的套接字变为被动连接。
listen函数在一般在调用bind之后-调用accept之前调用。

参数一:被listen函数调用的套接字;

参数二:连接数量的上限。

在服务器编程中,用户希望这个套接字可以接受外来的连接请求,也就是被动等待用户来连接。由于系统默认时认为一个套接字是主动连接的,所以需要通过某种方式来告诉系统,用户进程通过系统调用listen来完成这件事。

6.等待并接受连接(服务器端调用)

client=::accept(s,(SOCKADDR*)&remoteAddr,&nAddrLen);//accept默认会阻塞进程,直到有一个客户连接建立后返回,它返回的是一个新可用的套接字,这个套接字是连接套接字。
      if(client==INVALID_SOCKET)
	  {   
		  printf("Failed accept()\n");
	      continue;
	  }

对于服务器编程中最重要的一步等待并接受客户的连接,那么这一步在编程中如何完成,accept函数就是完成这一步的。它从内核中取出已经建立的客户连接,然后把这个已经建立的连接返回给用户程序,此时用户程序就可以与自己的客户进行点到点的通信了。

参数一:监听套接字,这个套接字用来监听一个端口,当有一个客户与服务器连接时,它使用这个一个端口号,而此时这个端口号正与这个套接字关联。当然客户不知道套接字这些细节,它只知道一个地址和一个端口号。

参数二:一个结果参数,它用来接受一个返回值,这返回值指定客户端的地址。

7.连接服务器(客户端调用)

/*连接服务器*/
  if(::connect(s,(sockaddr*)&servAddr,sizeof(servAddr))==-1)//返回:0---成功,-1---失败
  {   
	  printf("Failed connect()\n");
      ::WSACleanup();
	  system("pause");
  }

onnect函数完成主动连接的过程,connect函数的功能是完成一个有连接协议的连接过程,对于TCP来说就是那个三次握手过程。

面向连接的协议,在建立连接的时候总会有一方先发送数据,那么谁调用了connect谁就是先发送数据的一方。

参数一:指定数据发送的套接字;

参数二:指定数据发送的目的地址,也就是服务器端的地址。


一个简单的例子:

(服务器端)

/* 服务器端 */
#include
#include
#include
#include
#pragma comment(lib,"WS2_32.lib")

int main(int argc,char*argv[])
{
  /*加载winsock文件*/
  WSADATA wsaData;                    //WSADATA结构被用来储存调用AfxSocketInit全局函数返回的Windows Sockets初始化信息    定义在Winsock.h
  WORD sockVersion=MAKEWORD(2,0);     //使用WINSOCK2版本
 
  ::WSAStartup(sockVersion,&wsaData);   //第一个参数是WINSOCK 版本号,第二个参数是指向WSADATA的指针.
                                        //该函数返回一个INT型值,通过检查这个值来确定初始化是否成功   

  /*创建服务器端的套接字*/
  SOCKET s=::socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
                                    //三个参数分别代表 使用TCP/IP;传输过程使用TCP;不适用其他特殊协议

  if(s==INVALID_SOCKET)
  {   
	  printf("Failed socket()\n");
      ::WSACleanup();
      system("pause");
	  return 0;
  }

  /*socket中装入地址信息*/
  sockaddr_in sin;
  sin.sin_family=AF_INET;              //sin_family指代协议族,在socket编程中只能是AF_INET
  sin.sin_port=htons(13);              //表示服务器监听的端口号为13
  sin.sin_addr.S_un.S_addr=INADDR_ANY; //存储IP地址,INADDR_ANY就是指定地址为0.0.0.0的地址,这个地址事实上表示不确定地址,或“所有地址”、“任意地址”。

  /*绑定地址及端口号*/
  if(::bind(s,(LPSOCKADDR)&sin,sizeof(sin))==SOCKET_ERROR)
  {   
	  printf("Failed bind()\n");
      ::WSACleanup();
      system("pause");
	  return 0;
  }

  /*监听客户端的连接请求*/
  if(::listen(s,2)==SOCKET_ERROR)
  {   
	  printf("Failed listen()\n");
      ::WSACleanup();
      system("pause");
	  return 0;
  }

  sockaddr_in remoteAddr;
  int nAddrLen=sizeof(remoteAddr);
  SOCKET client;

  time_t t = time( 0 );
  char tmp[64];
  strftime( tmp, sizeof(tmp), "%Y/%m/%d %X %A\n\t", localtime(&t) );//提取系统时间

/*循环接受连接请求*/
  while(TRUE)
  {   
	  client=::accept(s,(SOCKADDR*)&remoteAddr,&nAddrLen);//accept默认会阻塞进程,直到有一个客户连接建立后返回,它返回的是一个新可用的套接字,这个套接字是连接套接字。
      if(client==INVALID_SOCKET)
	  {   
		  printf("Failed accept()\n");
	      continue;
	  }
	  printf("接收到一个客户端的连接:%s\r\n\n等待下一个客户端的连接……\n\n",inet_ntoa(remoteAddr.sin_addr));

	  ::send(client,tmp,strlen(tmp),0); /*发送本地时间给客户端*/

	  ::closesocket(client); /*关闭连接*/
  }

  ::closesocket(s);/*关闭套接字*/

  ::WSACleanup();
  system("pause");
  return 0;
}

(客户端)

/* 客户端  */
#include
#include
#include
#pragma comment(lib,"WS2_32.lib")

int main(int argc,char*argv[])
{
  /*加载winsock文件*/
  WSADATA wsaData;
  WORD sockVersion=MAKEWORD(2,0);
  ::WSAStartup(sockVersion,&wsaData);

  /*创建服务器端的套接字*/
  SOCKET s=::socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);

  if(s==INVALID_SOCKET)
  {   
	  printf("Failed socket()\n");
      ::WSACleanup();
	  system("pause");
  }

  /*socket中装入地址信息*/
  sockaddr_in servAddr;
  servAddr.sin_family=AF_INET;
  servAddr.sin_port=htons(13); /*接收服务器13端口号*/
  servAddr.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");/*本地IP地址为127.0.0.1*/

  /*连接服务器*/
  if(::connect(s,(sockaddr*)&servAddr,sizeof(servAddr))==-1)
  {   
	  printf("Failed connect()\n");
      ::WSACleanup();
	  system("pause");
  }

  /*接收数据并打印到屏幕上*/
  char buff[256];
  int nRecv=::recv(s,buff,256,0);
  if(nRecv>0)
  {   
	  buff[nRecv]='\0';
      printf("连接成功\n");
      printf("接收到数据:%s\n",buff);
      system("pause");
  }
  return 0;
}



推荐阅读
  • 本文介绍了如何清除Eclipse中SVN用户的设置。首先需要查看使用的SVN接口,然后根据接口类型找到相应的目录并删除相关文件。最后使用SVN更新或提交来应用更改。 ... [详细]
  • 本文介绍了为什么要使用多进程处理TCP服务端,多进程的好处包括可靠性高和处理大量数据时速度快。然而,多进程不能共享进程空间,因此有一些变量不能共享。文章还提供了使用多进程实现TCP服务端的代码,并对代码进行了详细注释。 ... [详细]
  • HDU 2372 El Dorado(DP)的最长上升子序列长度求解方法
    本文介绍了解决HDU 2372 El Dorado问题的一种动态规划方法,通过循环k的方式求解最长上升子序列的长度。具体实现过程包括初始化dp数组、读取数列、计算最长上升子序列长度等步骤。 ... [详细]
  • 动态规划算法的基本步骤及最长递增子序列问题详解
    本文详细介绍了动态规划算法的基本步骤,包括划分阶段、选择状态、决策和状态转移方程,并以最长递增子序列问题为例进行了详细解析。动态规划算法的有效性依赖于问题本身所具有的最优子结构性质和子问题重叠性质。通过将子问题的解保存在一个表中,在以后尽可能多地利用这些子问题的解,从而提高算法的效率。 ... [详细]
  • 本文介绍了三种方法来实现在Win7系统中显示桌面的快捷方式,包括使用任务栏快速启动栏、运行命令和自己创建快捷方式的方法。具体操作步骤详细说明,并提供了保存图标的路径,方便以后使用。 ... [详细]
  • 本文介绍了PE文件结构中的导出表的解析方法,包括获取区段头表、遍历查找所在的区段等步骤。通过该方法可以准确地解析PE文件中的导出表信息。 ... [详细]
  • Android源码深入理解JNI技术的概述和应用
    本文介绍了Android源码中的JNI技术,包括概述和应用。JNI是Java Native Interface的缩写,是一种技术,可以实现Java程序调用Native语言写的函数,以及Native程序调用Java层的函数。在Android平台上,JNI充当了连接Java世界和Native世界的桥梁。本文通过分析Android源码中的相关文件和位置,深入探讨了JNI技术在Android开发中的重要性和应用场景。 ... [详细]
  • CentOS 6.5安装VMware Tools及共享文件夹显示问题解决方法
    本文介绍了在CentOS 6.5上安装VMware Tools及解决共享文件夹显示问题的方法。包括清空CD/DVD使用的ISO镜像文件、创建挂载目录、改变光驱设备的读写权限等步骤。最后给出了拷贝解压VMware Tools的操作。 ... [详细]
  • 开发笔记:实验7的文件读写操作
    本文介绍了使用C++的ofstream和ifstream类进行文件读写操作的方法,包括创建文件、写入文件和读取文件的过程。同时还介绍了如何判断文件是否成功打开和关闭文件的方法。通过本文的学习,读者可以了解如何在C++中进行文件读写操作。 ... [详细]
  • 本文介绍了使用Python解析C语言结构体的方法,包括定义基本类型和结构体类型的字典,并提供了一个示例代码,展示了如何解析C语言结构体。 ... [详细]
  • 本文介绍了在Python中使用zlib模块进行字符串的压缩与解压缩的方法,并探讨了其在内存优化方面的应用。通过压缩存储URL等长字符串,可以大大降低内存消耗,虽然处理时间会增加,但是整体效果显著。同时,给出了参考链接,供进一步学习和应用。 ... [详细]
  • 本文讨论了在VMWARE5.1的虚拟服务器Windows Server 2008R2上安装oracle 10g客户端时出现的问题,并提供了解决方法。错误日志显示了异常访问违例,通过分析日志中的问题帧,找到了解决问题的线索。文章详细介绍了解决方法,帮助读者顺利安装oracle 10g客户端。 ... [详细]
  • 本文介绍了在Cpp中将字符串形式的数值转换为int或float等数值类型的方法,主要使用了strtol、strtod和strtoul函数。这些函数可以将以null结尾的字符串转换为long int、double或unsigned long类型的数值,且支持任意进制的字符串转换。相比之下,atoi函数只能转换十进制数值且没有错误返回。 ... [详细]
  • 使用freemaker生成Java代码的步骤及示例代码
    本文介绍了使用freemaker这个jar包生成Java代码的步骤,通过提前编辑好的模板,可以避免写重复代码。首先需要在springboot的pom.xml文件中加入freemaker的依赖包。然后编写模板,定义要生成的Java类的属性和方法。最后编写生成代码的类,通过加载模板文件和数据模型,生成Java代码文件。本文提供了示例代码,并展示了文件目录结构。 ... [详细]
  • SQL Server 2008 到底需要使用哪些端口?
    SQLServer2008到底需要使用哪些端口?-下面就来介绍下SQLServer2008中使用的端口有哪些:  首先,最常用最常见的就是1433端口。这个是数据库引擎的端口,如果 ... [详细]
author-avatar
没什么65丶1_750
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有