zoukankan      html  css  js  c++  java
  • socket编程—技术实现

    这几天都在玩socket了,有一点心得,贴出来与大家共赏,若有不妥或错误的地方,还请各位看官指点一二。
    什么是socket?socket就是...,我在这里就不抄书了,有兴趣的同仁去查查书吧。
    不过还要说一句,socket就是不同进程之间的一种通信方式。就象打电话是朋友之间的一种通信方式是一样。个人理解:所谓“通信”,就是相互之间发送数据。有人理解socket是不同计算机之间的一种通信方
    式,这是不确切的。两个进程,不管是运行在同一台计算机上,还是运行在不同计算机上,都可通过
    socket技术进行通信。
    socket套接字的使用需要有网卡的支持,所以socket一般都被用来在不同机器之间通信,而如果在同一台计算机上的两个进程进行通信,通常采用效率更高的共享内存技术来实现。
    两个进程之间进行通讯,就需要两个进程同时都在运行了(废话),在具体实现中,两个进程我们通常要区别对待,一个进程专门等待另一个进程给自己发消息,收到消息后进行处理,在把处理结果发送回去。我们把专门处理消息、提供服务的进程称为服务器端,把发送消息、请求处理的进程称为客户端。总体过程就是客户端发送一个消息给服务器端,服务器端进程收到消息进行处理,把处理结果发送给客户端。恩,就是这样。
    还有一个问题,如果我现在有一个进程要跟另一台计算机上的某个进程进行socket通信,那在我这个进程中如何指定另一个进程呢?这里还需要说一下另一个概念——端口,如果把操作系统比作一座房子的话,那端口就是房子的窗口,是系统外界同系统内部进行通信的通道。在socket实现中,我们不进行另一个进程的指定,而是指定发送消息或接收消息的端口号。比如说现在进程A要给进程B发消息,我们会把消息发送到进程B所运行的计算机的端口N上,而进程B此时正在监视端口N,这样进程B就能收到进程A发送来的数据,同样进程B也把消息发送到该端口上,进程A也能从该端口收到进程B发送来的数据,当然,这需要客户端和服务器端关于端口号进行一个约定,即共同操作同一个端口。如果客户端把消息发送到端口N1上,而服务器端监视的是端口N2,那通信一定不能成功。端口号最大为65535,不能比这个再大了,但在我们自己的程序中尽量不要用小于1024的端口号,小于1024的端口好很多都被系统使用了,比如23被telnet所使用。
    socket的实现是很简单的,只要按照一定的步骤,就可马上建立一个这样的通信通道。
    下面较详细的介绍几个核心的函数:
    SOCKET socket(int af, int type, int protocol);
    无论是客户端还是服务器端,下面这个函数是一定要用到的,也是最先用到的。
    这个函数是要告诉系统,给我准备好一个socket通道,我要和其它进程通信了。函数的返回值很重要,我们要记下来,它表示系统为我们准备好的这个socket通道,在以后的每个socket相关函数中都会用到,如果这个值等于SOCKET_ERROR,表示函数执行失败了。函数的参数我们分别给:PF_INET、SOCK_STREAM和IPPROTO_TCP。
    int bind(SOCKET s, const sockaddr *addr, int namelen);
    这个函数只有服务器端程序使用,作用是与某个socket通道绑定。可以用返回值判断该函数执行结果怎么样,如果等于SOCKET_ERROR,那就是失败了。第一个参数s,就是socket()函数的返回值;在结构addr中,我们要给定一个端口号;namelen等于结构sockaddr的大小。
    int listen(SOCKET s, int backlog);
    这个函数只有服务器端程序使用,作用是监听该端口。返回值与bind函数意义一样。
    int accept(SOCKET s, sockaddr *addr, int *addrlen);
    这个函数只有服务器端程序使用,作用是响应客户端的连接。返回值与bind函数意义一样。
    int connect(SOCKET s, const sockaddr *name, int namelen);
    这个函数只有客户端程序使用,作用是把客户端和某个计算机的某个端口建立连接。返回值与bind函数意义一样。第一个参数s,就是socket()函数的返回值;在结构name中,我们要给定一个端口号和目的机器名;namelen等于结构sockaddr的大小。
    int send(SOCKET s, char *buf, int len, int flags);
    int recv(SOCKET s, char *buf, int len, int flags);
    这两个函数就是发送数据和接收数据,客户端和服务器端程序都能用,哪个发送哪个接收不用说了吧?呵呵。
    从函数的返回值可以检查函数执行是否成功。参数中buf是指向发送或接收的数据的指针,len是数据长度。flags我们给个0就可以(其实是我不知道具体含义)。
    最后就是关闭socket了,这个很容易忘掉,但这个函数很重要,一定要用。
    int closesocket(SOCKET s);

    好了,关键函数就这么几个,下图是这几个函数的执行顺序:
     client端  service端
        |      |
        v      v
     socket()  socket()
        |      |
        |      v
        |   bind()
        |      |
        |      v
        |   listen()
        |      |
        |      v
        |   accept() 挂起,直到有客户端来连接
        |      |
        v    三段握手过程    |
     connect() <------------->  |
        |      |
        v    发送消息    v
       +---> send() ---------------> recv() <-------+
       |    |      .  |
       |    |        . 处理消息 |
       |    v    响应消息    .  |
       +---- recv() <--------------- send() --------+
        |      |
        v      |
     close() ---------------> recv()
           |
           v
        closesocket()
    上图我觉得能很好的说明客户端和服务器端的运行轨迹。
    使用以上几个函数在 linux 系统上就可成功建立一个socket通信连路,但如果在windows系统上,还要用到另一个函数:
    int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);
    在windows系统上,首先要执行这个函数,所以要把这个函数放在socket()函数的前面。
    我对上面的函数进行了一些封装,为节省篇幅,我去掉所有注释和非重要的函数,在这里可以看到各个函数的具体用法:
    在 VC60 环境下要运行下面的函数,要包含头文件 errno.h 和 winsock2.h,还有,在连接的时候要连接上ws2_32.dll文件。
    这是头文件内容:
    class Socket {
    public:
     bool setup();
     void close();
     bool connect(string host, int port);
     bool listen();
     int accept();
     int recv(char *buf, int len);
     int recv(int new_fd, char *buf, int len);
     int send(const char *msg, int len);
     int send(int new_fd, const char *msg, int len);
    private:
        int _fd;
    };
    这是实现文件内容:
    bool Socket::setup() {
     WSADATA wsd;
     _fd = WSAStartup(MAKEWORD(2,2), &wsd);  
     if(_fd) {
      return false;
     }
     _fd = ::socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
     if (_fd == -1) {
      return false;
     }
     return true;
    }
    bool Socket::listen() {
     struct sockaddr_in my_addr;
     
     my_addr.sin_family = AF_INET; 
     my_addr.sin_port = htons(52309);
     my_addr.sin_addr.s_addr = INADDR_ANY;
     
     if(::bind(_fd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == SOCKET_ERROR) {
      return false;
     }
     if(::listen(_fd, BACKLOG) == SOCKET_ERROR) {
      return false;
     }
     return true;
    }
    int Socket::accept()
    {
     int new_fd;
     struct sockaddr_in their_addr;
     int sin_size = sizeof(their_addr);
     
     printf("accepting... \n");
     new_fd = ::accept(_fd, 
       (struct sockaddr *)&their_addr,
       &sin_size);
     return new_fd == SOCKET_ERROR ? -1:new_fd;
    }
    bool Socket::connect(string host, int port) {
     struct hostent *_h = gethostbyname(host.c_str());
     if (_h == 0) {
      return false;
     }
     struct in_addr *_addr = (struct in_addr *)_h->h_addr;
     struct sockaddr_in sin;
     sin.sin_family = AF_INET;
     sin.sin_addr = *_addr;
     sin.sin_port = htons(port);
     if (::connect(_fd, (sockaddr *)&sin, sizeof(sin)) == SOCKET_ERROR) {
      return false;
     }
     return true;
    }
    int Socket::recv(int new_fd, char *buf, int len)
    {
     int nb = ::recv(new_fd, buf, len, 0);
     if (nb == -1) {
      printf("Error! recv.\n");
     }
     return nb;
    }
    int Socket::recv(char *buf, int len) {
     return recv(_fd, buf, len);
    }
    int Socket::send(const char *msg, int len) {
     return send(_fd, msg, len);
    }
    int Socket::send(int new_fd, const char *msg, int len)
    {
     int nb = ::send(new_fd, msg, len, 0);
     if (nb == -1) {
      printf("Error! send.\n");
     }
     return nb;
    }
    void Socket::close() {
     int trytimes = 0;
     while(::closesocket(_fd) && trytimes < CLOSE_TRY_TIMES)
      trytimes++;
     if(trytimes == 10) {
      printf("Cannot close socket!\n");
     }
    }
    好,socket类是封装好了,下面就是组织了,服务器端和客户端是不一样的,下面分别给出代码,到这里已经就很简单了。
    客户端:
    int main(int argc,  char **argv)
    {
     printf("socket of client is run ...\n");
     Socket s;
     if (!s.connect("dezhi", 52309))
      return 0;
     char *msg = "ok, send a message.";
     for (int i=0; i<10; i++) {
      s.send(msg, 20);
      printf("message = %s\n", msg);
     }
     s.send("q", 1);
     s.close();
     return 0;
    }
    服务器:
    int main(int argc,  char **argv) {
     printf("socket of service is run ...\n");
     Socket s;
     s.listen();
     int new_fd = s.accept();
     char buf[8];
     buf[7] = '\0';
     while (1) {
      if (s.recv(new_fd, buf, 5) != -1) {
       printf("%s\n", buf);
       if (buf[0] == 'q')
        break;
      }
     }
     s.close();
    }
    下面为运行结果:
    客户端:
    socket of client is run ...
    Socket: WSAStartup success execute.
    Socket: socket success execute.
    Socket: Establish the connection to "127.0.0.1:52309"
    message = ok, send a message.
    message = ok, send a message.
    message = ok, send a message.
    message = ok, send a message.
    message = ok, send a message.
    message = ok, send a message.
    message = ok, send a message.
    message = ok, send a message.
    message = ok, send a message.
    message = ok, send a message.
    Socket: Close connection to "127.0.0.1:52309"
    Press any key to continue
    服务器端
    socket of service is run ...
    Socket: WSAStartup success execute.
    Socket: socket success execute.
    bind ok!
    listen ok!
    accepting...
    ok, send a message.
    ok, send a message.
    ok, send a message.
    ok, send a message.
    ok, send a message.
    ok, send a message.
    ok, send a message.
    ok, send a message.
    ok, send a message.
    ok, send a message.
    qk, send a message.
    Press any key to continue
    就到这里吧。socket的相关内容可远不止这些,我在这里只是给大家来个抛砖引玉,想深究?路还很漫长。关于详细的实现代码,去我的《源码》上找吧,不放在这里,是为了让篇幅小些。
  • 相关阅读:
    LeetCode 42. Trapping Rain Water
    LeetCode 209. Minimum Size Subarray Sum
    LeetCode 50. Pow(x, n)
    LeetCode 80. Remove Duplicates from Sorted Array II
    Window10 激活
    Premiere 关键帧缩放
    AE 「酷酷的藤」特效字幕制作方法
    51Talk第一天 培训系列1
    Premiere 视频转场
    Premiere 暴徒生活Thug Life
  • 原文地址:https://www.cnblogs.com/qq78292959/p/2077112.html
Copyright © 2011-2022 走看看