Socket 套接字

posted at 2023.2.23 04:31 by Administrator

为了更方便地开发网络应用程序,美国伯克利大学UNIX上推出了一种应用程序访问通信协议的操作系统调用接字(Socket) Socket的出现,使得程序员可以很方便地访问 TCPIP,从而开发各种网络应用程序。后来套接字被引进到 Windows等操作系统,成为开发网络应用程序的有效工具。

所谓套接字(Socket),就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。一个套接字就是网络上进程通信的一端,提供了应用层进程利用网络协议交换数据的机制。从所处的地位来讲,套接字上联应用进程,下联网络协议栈,是应用程序通过网络协议进行通信的接口,是应用程序与网络协议栈进行交互的接口。

套接字包括 IP 地址和端口号两个部分。通过网络通信的每对进程需要使用一对套接字。不同的进程之间的通信所使用的套接字是不一样的,套接字可以用来区分不同的进程之间的数据传输。套接字主要有目标IP、传输层使用的传输协议、传输层使用的端口号这3个重要参数。

Socket处于网络协议的传输层套接字可以分为流套接字SOCK_STREAM、数据报套接字SOCK_DGRAM和原始套接字SOCK_RAW 3种不同的类型。流套接字使用TCP(The Transmission Control Protocol)协议进行数据的传输,数据报套接字使用UDP( User DatagramProtocol)协议进行数据的传输,原始套接字主要用于一些协议的开发,可以进行比较底层的操作。

接下来介绍如何使用Socket编程来实现TCP通信。

TCP面向字节流传输数据,提供可靠的数据传输服务。通过TCP传送的数据无差错、不丢失、不重复,按序到达。由于TCP是基于连接的,所以每一条连接只能是点对点的交互通信。

一、函数介绍

Socket函数

    根据指定的地址族、数据类型和协议来分配一个套接口的描述字及其所用资源的函数。

 SOCKET WSAAPI Socket(
_In_ int af,
_In_ int type,
_In_ int protocol )

    Bind函数

            将本地地址与套接字相关联。

       int bind(
 _In_ SOCKET       s,
 _In_ const struct sockaddr *name,
_In_ int        namelen )

       Htons函数

           将整型变量从主机字节顺序转变成网络字节顺序,就是整数在地址空间中存储方式变为高位

       字节存放在内存的低地址处。

                                           u_short WSAAPI htons(
               _In_ u_short hostshort )

       Inet_addr函数

           将一个点分十进制的IP转换成一个长整型数。

         unsigned long inet_addr(
              _In_ const char *cp )

   Listen函数

 将一个套接字置于正在监听传入连接的状态。

        int listen(
         _In_ SOCKET   s,
         _In_ int    backlog )

       Accept函数

           允许在套接字上尝试连接。

         SOCKET accept(
           _In_  SOCKET        s,
           _Out_  struct sockaddr *addr,
           _Inout_  int        *addrlen )

   Send函数

       在建立连接的套接字上发送数据。

        int_send(
           _In_     SOCKET   s,
           _In_  const  char   *buf,
           _In_    int   len,
           _In_    int   flags )

               Recv函数

           从连接的套接字或绑定的无连接套接字中接收数据。

        int recv(
           _In_     SOCKET   s,
           _Out_  char   *buf,
           _In_    int   len,
           _In_    int   flags ) 

   二、实现原理

   无论是服务器端还是客户端,都要先初始化Winsock服务环境。服务器端初始化环境后,便调用Socket函数创建流式套接字;然后对sockaddr_in结构体进行设置,设置服务器绑定的IP地址和端口等信息并调用bind函数来绑定;绑定成功后就可以调用listen函数设置连接数量,并进行监听。直到有来自客户端的连接请求,服务器便调用accept函数接受连接请求,建立连接。这时,可以使用recv函数和send函数与客户端进行数据收发。通信结束后,关闭套接字,释放资源。

   客户端初始化环境后,便调用Socket函数创建流式套接字;然后对sockaddr_in结构体进行设置,设置服务器的IP地址和端口等信息并调用connect函数向服务器发送连接请求,并等待服务器响应。服务器接受连接请求后,就成功地与服务器建立连接,这时,可以使用recv函数和send函数与客户端进行数据收发。通信结束后,关闭套接字,释放资源。

TCP Socket 通信流程图如下所示:

     三、编程实现

接下来分别给出服务端以及客户端的具体实现代码。

服务端代码:

#include"stdafx.h"
 
int_tmain(intargc, _TCHAR* argv[])
{
        // 创建套接字并绑定地址端口进行监听
        if (FALSE == SocketBindAndListen("127.0.0.1", 12345))
        {
                 printf("SocketBindAndListen Error.\n");
        }
        printf("SocketBindAndListen OK.\n");
 
        // 发送信息
        char szSendBuf[MAX_PATH] = {0};
        while (TRUE)
        {
                 gets(szSendBuf);
                 // 发送数据
                 SendMsg(szSendBuf);
        }
 
        return 0;
}
// 服务端套接字
SOCKET g_ServerSocket;
// 客户端套接字
SOCKET g_ClientSocket; 
// 绑定端口并监听
BOOL SocketBindAndListen(char *lpszIp, intiPort)
{
        // 初始化 Winsock
        WSADATA wsaData = {0};
        ::WSAStartup(MAKEWORD(2, 2), &wsaData); 
        // 创建流式套接字
        g_ServerSocket = ::socket(AF_INET, SOCK_STREAM, 0);
        if (INVALID_SOCKET == g_ServerSocket)
        {
                 returnFALSE;
        }
        // 设置服务端地址和端口信息
        sockaddr_in addr;
        addr.sin_family = AF_INET;
        addr.sin_port = ::htons(iPort);
        addr.sin_addr.S_un.S_addr = ::inet_addr(lpszIp);
        // 绑定IP和端口
        if (0 != ::bind(g_ServerSocket, (sockaddr *)(&addr), sizeof(addr)))
        {
                 returnFALSE;
        }
        // 设置监听
        if (0 != ::listen(g_ServerSocket, 1))
        {
                 returnFALSE;
        } 
        // 创建接收数据多线程
       ::CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)RecvThreadProc, NULL, NULL, NULL); 
        returnTRUE;
} 
// 发送数据
void SendMsg(char *pszSend)
{
        // 发送数据
        ::send(g_ClientSocket, pszSend, (1 + ::lstrlen(pszSend)), 0);
        printf("[send]%s\n", pszSend);
} 
// 接受连接请求接收数据
void AcceptRecvMsg()
{
        sockaddr_in addr = { 0 };
        // 注意:该变量既是输入也是输出
        int iLen = sizeof(addr);  
        // 接受来自客户端的连接请求
        g_ClientSocket = ::accept(g_ServerSocket, (sockaddr *)(&addr), &iLen);
        printf("accept a connection from client!\n"); 
        char szBuf[MAX_PATH] = { 0 };
        while (TRUE)
        {
                 // 接收数据
                 int iRet = ::recv(g_ClientSocket, szBuf, MAX_PATH, 0);
                 if (0 >= iRet)
                 {
                         continue;
                 }
                 printf("[recv]%s\n", szBuf);
        }
} 
// 接收数据多线程
UINT RecvThreadProc(LPVOIDlpVoid)
{
        // 接受连接请求接收数据
        AcceptRecvMsg();
        return 0;
} 

客户端代码:

#include"stdafx.h" 
int_tmain(intargc, _TCHAR* argv[])
{
        // 连接到服务器
        if (FALSE == Connection("127.0.0.1", 12345))
        {
                 printf("Connection Error.\n");
        }
        printf("Connection OK.\n"); 
        // 发送信息
        char szSendBuf[MAX_PATH] = { 0 };
        while (TRUE)
        {
                 gets(szSendBuf);
                 // 发送数据
                 SendMsg(szSendBuf);
        } 
        return 0;
} 
// 客户端套接字
SOCKET g_ClientSocket; 
// 连接到服务器
BOOL Connection(char *lpszServerIp, intiServerPort)
{
        // 初始化 Winsock
        WSADATA wsaData = { 0 };
        ::WSAStartup(MAKEWORD(2, 2), &wsaData);
        // 创建流式套接字
        g_ClientSocket = ::socket(AF_INET, SOCK_STREAM, 0);
        if (INVALID_SOCKET == g_ClientSocket)
        {
                 returnFALSE;
        }
        // 设置服务端地址和端口信息
        sockaddr_in addr = { 0 };
        addr.sin_family = AF_INET;
        addr.sin_port = ::htons(iServerPort);
        addr.sin_addr.S_un.S_addr = ::inet_addr(lpszServerIp);
        // 连接到服务器
        if (0 != ::connect(g_ClientSocket, (sockaddr *)(&addr), sizeof(addr)))
        {
                 returnFALSE;
        }
        // 创建接收数据多线程
       ::CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)RecvThreadProc, NULL, NULL, NULL); 
        returnTRUE;
} 
// 发送数据
void SendMsg(char *pszSend)
{
        // 发送数据
        ::send(g_ClientSocket, pszSend, (1 + ::lstrlen(pszSend)), 0);
        printf("[send]%s\n", pszSend);
}
// 接收数据
void RecvMsg()
{
        char szBuf[MAX_PATH] = { 0 };
        while (TRUE)
        {
                 // 接收数据
                 int iRet = ::recv(g_ClientSocket, szBuf, MAX_PATH, 0);
                 if (0 >= iRet)
                 {
                         continue;
                 }
                 printf("[recv]%s\n", szBuf);
        }
} 
// 接收数据多线程
UINT RecvThreadProc(LPVOIDlpVoid)
{
        // 接受连接请求接收数据
        RecvMsg();
        return 0;
} 
 

    四、测试效果图:

/p>

 

Tags: , , , ,

IT技术

Add comment

  Country flag

biuquote
  • Comment
  • Preview
Loading