zoukankan      html  css  js  c++  java
  • Socket编程入门

     

    socket基本知识

    网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket。

    建立网络通信连接至少要一对端口号(socket)。socket本质是编程接口(API),对TCP/IP的封装,TCP/IP也要提供可供程序员做网络开发所用的接口,这就是Socket编程接口;HTTP是轿车,提供了封装或者显示数据的具体形式;Socket是发动机,提供了网络通信的能力。

    Java Socket的通信时序图如下。

    Java Socket的数据通信模型如下。

    Java编程

    • 通信步骤

    Server端

    Client端

    1. 创建ServerSocker
    2. 绑定端口
    3. 等待端口的通信请求(此步会返回一个socket,这个socket作为server端的socket)
    4. 建立server端的socket的输入流(reader)和输出流(writer)
    5. reader可以获取client的通信数据,writer可以向client发送数据
    1. 创建一个socket
    2. 连接IP:port(要求server存在)
    3. 建立client的输入流(reader)和输出流(writer)
    4. reader可以获取server的通信数据,writer可以向server发送数据
    • socket简单对话

    ·Server端

    public class TestServer {

        public static final String datePattern = "yyyy-MM-dd HH:mm:ss SSS";

        public static final SimpleDateFormat dateFormat = new SimpleDateFormat(datePattern);

        

        public static void main(String[] args) throws IOException

        {

            //1.创建一server socket服务

            ServerSocket serverSocket = new ServerSocket();

            //2.绑定端口

            InetSocketAddress address = new InetSocketAddress("localhost", 18824);

            serverSocket.bind(address);

            //3.等待和接收端口的通信请求,返回的是一个socket

            PrintConsoleMsg("等待连接...");

            Socket socket = serverSocket.accept();

            PrintConsoleMsg("连接成功!");

            

            //服务端的输入与输出

            BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));

            PrintWriter writer = new PrintWriter(socket.getOutputStream(), true); //true表示autoflush

            

            //获取键盘输入

            BufferedReader keyboard = new BufferedReader(new InputStreamReader(System.in));

            

            while(true)

            {

                if(reader.ready())

                {

                    //捕获clientsocket发来的消息

                    PrintClientMsg(reader.readLine());

                }

                if(keyboard.ready())

                {

                    //捕获当前server的键盘输入

                    String content = keyboard.readLine();

                    //打印在server的屏幕

                    PrintConsoleMsg(content);

                    //发送到client

                    writer.println(content);

                }

            }

            

        }

        

        public static void PrintConsoleMsg(String msg)

        {

            System.out.println("Server: " + dateFormat.format(new Date()) + " " + msg);

        }

        

        public static void PrintClientMsg(String msg)

        {

            System.out.println("Client: " + dateFormat.format(new Date()) + " " + msg);

        }

    }

     

    ·Client端

    public class TestClient {

        public static final String datePattern = "yyyy-MM-dd HH:mm:ss SSS";

        public static final SimpleDateFormat dateFormat = new SimpleDateFormat(datePattern);

        public static void main(String[] args) throws UnknownHostException, IOException {

            

            //1.创建一个socket

            Socket socket = new Socket();

            //2.连接serverIP:端口

            InetSocketAddress address = new InetSocketAddress("localhost", 18824);

            socket.connect(address);

            //3.client socket的输入流和输出流

            BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));

            PrintWriter writer = new PrintWriter(socket.getOutputStream(),true);

            //当前client的键盘输入流

            BufferedReader keyboard = new BufferedReader(new InputStreamReader(System.in));

            

            while(true)

            {

                if(reader.ready())

                {

                    PrintServerMsg(reader.readLine());

                }

                if(keyboard.ready())

                {

                    String content = keyboard.readLine();

                    //打印在clientconsole

                    PrintConsoleMsg(content);

                    //发送给server

                    writer.println(content);

                }

            }

        }

        

        public static void PrintConsoleMsg(String msg)

        {

            System.out.println("Client: " + dateFormat.format(new Date()) + " " + msg);

        }

        

        public static void PrintServerMsg(String msg)

        {

            System.out.println("Server: " + dateFormat.format(new Date()) + " " + msg);

        }

    }

     

    ·运行结果

    打开2个power shell分别作为server和client端。

    1. 运行server端

    1. 运行client进行连接

    1. client输入信息,模拟通信

    1. server输入回复信息

    1. client输入回复信息

     

    • 多客户端与单服务器

    ·server端

    public class MyServer {

        public static void main(String[] args) throws IOException

        {

            ServerSocket serverSocket = new ServerSocket();

            InetSocketAddress address = new InetSocketAddress("localhost", 18824);

            serverSocket.bind(address);

            

            while(true)

            {

                Socket socket = serverSocket.accept();

                System.out.println("Client " + socket.getPort() + "连接成功!");

                new Thread(new SocketHandler(socket)).start();

            }

        }

    }

     

    class SocketHandler implements Runnable{

        public static final String datePattern = "yyyy-MM-dd HH:mm:ss SSS";

        public static final SimpleDateFormat dateFormat = new SimpleDateFormat(datePattern);

        private Socket socket;

        

        public SocketHandler(Socket socket) {

            // TODO Auto-generated constructor stub

            this.socket = socket;

        }

        

        @Override

        public void run() {

            // TODO Auto-generated method stub

            try

            {

                BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));

                PrintWriter writer = new PrintWriter(socket.getOutputStream(),true);

                BufferedReader keyboard = new BufferedReader(new InputStreamReader(System.in));

                boolean flag = true;

                while(flag)

                {

                    if(reader.ready())

                    {

                        String content = reader.readLine();

                        PrintClientMsg(content, socket.getPort());    //getPort获取client的端口

                        if(content == "exit")

                        {

                            flag=false;

                            socket.close();

                        }

                    }

                    if(keyboard.ready())

                    {

                        String content = keyboard.readLine();

                        writer.println(content);

                        PrintServerMsg(content);

                    }

                }

            } catch (Exception e) {

                // TODO: handle exception

                e.printStackTrace();

            }

        }

        

        public static void PrintServerMsg(String msg)

        {

            System.out.println("Server: " + dateFormat.format(new Date()) + " " + msg);

        }

        public static void PrintClientMsg(String msg, int port)

        {

            System.out.println("Client " + port + ": " + dateFormat.format(new Date()) + " " + msg);

        }

    }

     

    • client端

    public class MyClient {

        public static final String datePattern = "yyyy-MM-dd HH:mm:ss SSS";

        public static final SimpleDateFormat dateFormat = new SimpleDateFormat(datePattern);

        public static void main(String[] args) throws UnknownHostException, IOException {

            Socket socket = new Socket("localhost", 18824);

            BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));

            PrintWriter writer = new PrintWriter(socket.getOutputStream(),true);

            BufferedReader keyboard = new BufferedReader(new InputStreamReader(System.in));

            while(true)

            {

                if(reader.ready())

                {

                    String content = reader.readLine();

                    PrintServerMsg(content);

                }

                

                if(keyboard.ready())

                {

                    String content = keyboard.readLine();

                    writer.println(content);

                    PrintClientMsg(content);

                    if(content=="exit")

                    {

                        reader.close();

                        writer.close();

                        keyboard.close();

                        socket.close();

                    }

                }

            }

        }

        

        public static void PrintServerMsg(String msg)

        {

            System.out.println("Server: " + dateFormat.format(new Date()) + " " + msg);

        }

        public static void PrintClientMsg(String msg)

        {

            System.out.println("Client: " + dateFormat.format(new Date()) + " " + msg);

        }

     

    }

     

    • 单个client的运行结果

    • 2个client的运行结果

     

    • 结果分析

    对于单个client的情况:client与server能够双向通信。

    对于k个(k>=2)client的情况:每个client都能够发信息到server。server也能发信息到每一个client,但是对于是哪一个client接收却无法确定(可能跟多线程管理有关)。

     

    C++编程

    • 常用函数

    函数

    说明

    uint16_t htons(uint16_t hostshort);

    htons是将整型变量从主机字节顺序转变成网络字节顺序, 就是整数在地址空间存储方式变为高位字节存放在内存的低地址处。(小端->大端)

    int socket (int domain,

    int type, int protocol);

    建立一个协议族为domain、协议类型为type、协议编号为protocol的套接字文件描述符。如果函数调用成功,会返回一个标识这个套接字的文件描述符,失败的时候返回-1。

    int bind (SOCKET socket,

    struct sockaddr* address,

    socklen_t address_len);

    socket:是一个套接字描述符。

    address:是一个sockaddr结构指针,该结构中包含了要结合的地址和端口号。(sockaddr与sockaddr_in等价)

    address_len:确定address缓冲区的长度。

    返回值:如果函数执行成功,返回值为0,否则为SOCKET_ERROR。

    int listen(int fd, int backlog);

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

    listen函数一般在调用bind之后-调用accept之前调用。

    fd 一个已绑定未被连接的套接字描述符

    backlog 连接请求队列(queue of pending connections)

    的最大长度(一般由2到4)。

    无错误,返回0,否则-1

    int recv (SOCKET socket,

    char FAR* buf, int len, int flags);

    socket:一个标识已连接套接口的描述字。

    buf:用于接收数据的缓冲区。

    len:缓冲区长度。

    flags:指定调用方式。取值:MSG_PEEK 查看当前数据,数据将被复制到缓冲区中,但并不从输入队列中删除;MSG_OOB 处理带外数据。

    返回值:

    若无错误发生,recv()返回读入的字节数。如果连接已中止(另一端终止了连接),返回0。否则的话,返回SOCKET_ERROR错误,应用程序可通过WSAGetLastError()获取相应错误代码。

    int accept(int sockfd,

    void *addr, int *addrlen);

    sockfd: server端的socket fd

    addr和addrlen:client的sockaddr_in

    成功返回一个新的套接字描述符,失败返回-1。

    ssize_t send (int s,

    const void *msg,

    size_t len, int flags);

    s指定发送端套接字描述符;

    第二个参数指明一个存放应用程式要发送数据的缓冲区;

    第三个参数指明实际要发送的数据的字符数;

    第四个参数一般置0。

    in_addr_t inet_addr

    (const char* strptr);

    将一个点分十进制的IP转换成一个长整数型数(u_long类型)

     

    • domain的含义

    名称

    含义

    名称

    含义

    PF_UNIX,PF_LOCAL

    本地通信

    PF_X25

    ITU-T X25 / ISO-8208协议

    AF_INET, PF_INET

    IPv4 Internet协议

    PF_AX25

    Amateur radio AX.25

    PF_INET6

    IPv6 Internet协议

    PF_ATMPVC

    原始ATM PVC访问

    PF_IPX

    IPX-Novell协议

    PF_APPLETALK

    Appletalk

    PF_NETLINK

    内核用户界面设备

    PF_PACKET

    底层包访问

     

    • type含义

    名称

    含义

    SOCK_STREAM

    Tcp连接,提供序列化的、可靠的、双向连接的字节流。支持带外数据传输

    SOCK_DGRAM

    支持UDP连接(无连接状态的消息)

    SOCK_SEQPACKET

    序列化包,提供一个序列化的、可靠的、双向的基本连接的数据传输通道,数据长度定常。每次调用读系统调用时数据需要将全部数据读出

    SOCK_RAW

    RAW类型,提供原始网络协议访问

    SOCK_RDM

    提供可靠的数据报文,不过可能数据会有乱序

    SOCK_PACKET

    这是一个专用类型,不能呢过在通用程序中使用

     

    • protocol含义

    函数socket()的第3个参数protocol用于制定某个协议的特定类型,即type类型中的某个类型。通常某协议中只有一种特定类型,这样protocol参数仅能设置为0;但是有些协议有多种特定的类型,就需要设置这个参数来选择特定的类型。

    ·SOCK_STREAM的套接字表示一个双向的字节流,与管道类似。流式的套接字在进行数据收发之前必须已经连接,连接使用connect()函数进行。一旦连接,可以使用read()或者write()函数进行数据的传输。流式通信方式保证数据不会丢失或者重复接收,当数据在一段时间内任然没有接受完毕,可以将这个连接人为已经死掉。

    ·SOCK_DGRAM和SOCK_RAW 这个两种套接字可以使用函数sendto()来发送数据,使用recvfrom()函数接受数据,recvfrom()接受来自制定IP地址的发送方的数据。

    ·SOCK_PACKET是一种专用的数据包,它直接从设备驱动接受数据。

     

    • 通信步骤

    • 名词解析

    htonl()--"Host to Network Long"

    ntohl()--"Network to Host Long"

    htons()--"Host to Network Short"

    ntohs()--"Network to Host Short"

    AF--Address Family

    PF—Procotol Family

    • socket简单对话

    实现功能:server和client"一问一答"的对话。

    ·helper.c

    #include <time.h>

    void print_time()

    {

        time_t t;

        struct tm *lt;

        time(&t);

        lt = localtime(&t);

        printf(" %d/%d/%d %d:%d:%d ", lt->tm_year+1900, lt->tm_mon, lt->tm_mday, lt->tm_hour, lt->tm_min, lt->tm_sec);

    }

     

    ·server端

    #define MYPORT 8887

    #define QUEUE 20

    #define BUFFER_SIZE 1024

    int main()

    {

        //定义socket fd

        int server_socket_fd = socket(AF_INET, SOCK_STREAM, 0);

        //定义sockaddr in

        //'sin' means socket input

        struct sockaddr_in server_sinaddr;

        server_sinaddr.sin_family = AF_INET; //协议

        server_sinaddr.sin_port = htons(MYPORT);//端口

        server_sinaddr.sin_addr.s_addr = htonl(INADDR_ANY);//IP

     

        //bind success?

        //bind server_socket_fd with server_sinaddr

        if(bind(server_socket_fd, (struct sockaddr *)&server_sinaddr, sizeof(server_sinaddr)) == -1)

        {

            perror("bind error ");

            exit(-1);

        }

        printf("bind success... ");

        //listen success?

        //waiting for connecting

        //QUEUE means 20 requests is permitted

        if(listen(server_socket_fd, QUEUE) == -1)

        {

            perror("listen error ");

            exit(-1);

        }

        printf("waitting connect request... ");

        //client

        char buffer[BUFFER_SIZE] = { 0 };

        struct sockaddr_in client_sinaddr;

        socklen_t length = sizeof(client_sinaddr);

     

        //client_socket_fd 是一个已连接socket fd

        int client_socket_fd = accept(server_socket_fd, (struct sockaddr*)&client_sinaddr, &length);

        if(client_socket_fd < 0)

        {

            perror("connect error ");

            exit(-1);

        }

     

        print_time();

        printf(" : connect success ");

          

        char reply[BUFFER_SIZE];

        while(1)

        {

            memset(buffer, 0, sizeof(buffer));

            int len = recv(client_socket_fd, buffer, sizeof(buffer), 0);

            if(strcmp(buffer, "exit ") == 0 || strcmp(buffer,"exit") == 0)

            {

                print_time();

                printf("exit... ");

                break;

            }

            printf("Client ");

            print_time();

            fputs(buffer, stdout);

            //send(client_socket_fd, buffer, len, 0);//此处应该是向client返回同样的数据

            fgets(reply, sizeof(reply), stdin);

            send(client_socket_fd, reply, strlen(reply), 0);

        }

        close(client_socket_fd);

        close(server_socket_fd);

        return 0;

    }

    ·client端

    #define MYPORT 8887

    #define BUFFER_SIZE 1024

    int main()

    {

        int client_socket_fd = socket(AF_INET, SOCK_STREAM, 0);

     

        //sock addr

        struct sockaddr_in client_sinaddr;

        client_sinaddr.sin_family = AF_INET;

        client_sinaddr.sin_port = htons(MYPORT);

        client_sinaddr.sin_addr.s_addr = inet_addr("127.0.0.1");

     

        //connect to server

        if(connect(client_socket_fd, (struct sockaddr*)&client_sinaddr, sizeof(client_sinaddr)) < 0)

        {

            perror("connect error ");

            exit(-1);

        }

        print_time();

        printf(" connect to server successfully... ");

        char send_buf[BUFFER_SIZE];

        char recv_buf[BUFFER_SIZE];

        while(fgets(send_buf, sizeof(send_buf), stdin) != NULL)

        {

            send(client_socket_fd, send_buf, strlen(send_buf), 0);

            printf("Client ");

            print_time();

            fputs(send_buf, stdout);

            if(strcmp(send_buf, "exit ") == 0 || strcmp(send_buf, "exit") == 0)

            {

                print_time();

                printf("exit... ");

                break;

            }

            recv(client_socket_fd, recv_buf, sizeof(recv_buf), 0);

            printf("Server ");

            print_time();

            fputs(recv_buf, stdout);

            memset(send_buf, 0, sizeof(send_buf));

            memset(recv_buf, 0, sizeof(recv_buf));

        }

        close(client_socket_fd);

        return 0;

    }

    ·运行结果

    连接成功

    client向server发送消息

    server回复client

    后续1

    后续2

    输入exit

    • 参考文献

    https://blog.csdn.net/luanlouis/article/details/19974999

    https://blog.csdn.net/xc_tsao/article/details/44123331

    https://www.cnblogs.com/xudong-bupt/p/3483059.html

  • 相关阅读:
    初尝2D骨骼动画编辑工具SPINE,并into Unity3D
    Unity3D优化总结(一)
    ARPG游戏打击感相关的技术简单总结
    总结一下一般游戏中3D模型各种勾边方法遇到的工程性问题
    多次被坑了后,重温设计模式原则
    unity shader序列帧动画代码,顺便吐槽一下unity shader系统
    暗黑三泛光算法研究
    《全民无双》锁链刀光程序生成算法,即U3D——刀光轨迹插件的改进
    Low-level Thinking in High-level Shading Languages
    python实现智能语音天气预报
  • 原文地址:https://www.cnblogs.com/sinkinben/p/10580885.html
Copyright © 2011-2022 走看看