zoukankan      html  css  js  c++  java
  • Linux C++/Java/Web/OC Socket网络编程

    一,Linux C++ Socket网络编程

    1.什么是TCP/IPUDP

      TCP/IPTransmission Control Protocol/Internet Protocol)即传输控制协议/网间协议,是一个工业标准的协议集,它是为广域网(WANs)设计的。
      UDPUser Data Protocol,用户数据报协议)是与TCP相对应的协议。它是属于TCP/IP协议族中的一种。
      下面的图表明了这些协议的关系。

     

    2.Socket在哪里呢?

    3.Socket是什么呢?

      Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

      门面模式,用自己的话说,就是系统对外界提供单一的接口,外部不需要了解内部的实现。

    4.有很多的框架,为什么还在从Socket开始?

      现在的跨平台网络编程框架很多,如Java的SSH,C/C++的Boost等。

      现在的分布式框架很多,如Hadoop等。

      Socket是分布式、云计算、网络编程的基础,对Socket的学习有利于对其他框架的理解。

      下图是Socket编程的基本流程:

    第一步:建立一个socket

    int socket(int af, int type, int protocol) 

    A. 'int af'代表地址族或者称为socket所代表的域,通常有两个选项: 
        1. AF_UNIX - 只在单机上使用。 
        2. AF_INET - 可以在单机或其他使用DARPA协议(UDP/TCP/IP)的异种机通信。 
    B. 'int type'代表你所使用的连接类型,通常也有两种情况: 
        1. SOCK_STREAM - 用来建立面向连接的sockets,可以进行可靠无误的的数据传输 
        2. SOCK_DGRAM - 用来建立没有连接的sockets,不能保证数据传输的可靠性。
    C. 'int protocol'通常设定为0。使系统选择默认的由协议族和连接类型所确定的协议。
    D. 返回值是一个文件描述句柄,如果在此期间发生错误则返回-1并且设定了相应的errno。 

    int sockfd;
        if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
            perror("socket 创建出错!");
            exit(1);
        }

    第二步:绑定名字socket: bind() 

    int bind(int sockfd, struct sockaddr *name, int namelen) 

    A. sockfd是从socket()调用得到的文件描述句柄。

    B. name是一个指向sockaddr类型结构的一个指针。

    C. namelen给出了文件名的具体长度。

    在使用AF_INET地址族的时候,类型定义如下:

    struct sockaddr_in {
        short int sin_family;
        unsigned short int sin_port;
        struct in_addr sin_addr;
        unsigned char sin_zero[8];
    };

    在这个结构种,name.sin_family应当被设定为AF_INET

    struct sockaddr_in name;
    
        int sockfd;
        if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
            exit(1);
        }
    
        name.sin_family = AF_INET;
        name.sin_port = htons(8087);
        name.sin_addr.s_addr = htonl(INADDR_ANY);
        bzero(&(name.sin_zero), 8); /* zero out the rest of the space */
    
        if (bind(sockfd, (struct sockaddr *) &name, sizeof(struct sockaddr))
                == -1) {
            exit(1);
        }

    现在,如果没有问题的话,我们建立的socket就有一个名字了!相反,如果不成功,它会设定相应的错误代码,并使程序退出。

    第三步:远程连接: connect() 

    int connect(int sockfd, struct sockaddr *serv_addr, int addrlen)

    A. sockfd是我们建立的文件描述句柄

    B. serv_addr是一个sockaddr结构,包含目的的地址和端口号

    C. addrlen 被设定为sockaddr结构的大小。 

    if (connect(sockfd, (struct sockaddr *) &name, sizeof(name)) < 0) {  exit(1); }

    第四步:监听: listen() 

    int listen(int sockfd, int backlog)

    B. 参数backlog是指一次可以监听多少个连接 

    if (listen(sockfd, 20) == -1) { exit(1); }

    第五步:阻塞接受连接: accept()

    当有人试图从我们打开的端口登陆进来时,我们应该响应他,这个时候就要用到accept()函数了。

    int accept(int sockfd, void *addr, int *addrlen)

    conn = accept(sockfd, (struct sockaddr*) &name, sizeof(name));
        if (conn < 0) {
            exit(1);
        }

    第六步:输入和输入的完成: send() and recv() 

    int send(int sockfd, const void *msg, int len, int flags)
    int recv(int sockfd, void *buf, int len, unsigned int flags)

    send(): 
    sockfd - socket file descriptor 
    msg - message to send 
    len - size of message to send 
    flags - read 'man send' for more info, set it to 0 for now 

    recv(): 
    sockfd - socket file descriptor 
    buf - data to receive 
    len - size of buf 
    flags - same as flags in send() 

    注意:如果使用的连接类型是SOCK_DGRAM,那么应该使用sendto()和recvfrom()来实现数据传输。

    char buffer[BUFFER_SIZE];
    while (1) {
            memset(buffer, 0, sizeof(buffer));
            int len = recv(conn, buffer, sizeof(buffer), 0);
            if (strcmp(buffer, "exit
    ") == 0)
                break;
            fputs(buffer, stdout);
            send(conn, buffer, len, 0);
        }

    结束: close() and shutdown() 

    当传输结束时,应当关闭连接。

    一个服务端等待, 客户端上传文件到服务端,通过输入要上传的文件名,目前只做到仅对当前执行文件的目录下的文件,应该在服务端收到文件路径之后进行处理的。

    服务端代码:

    #include <stdio.h>
    #include <stdlib.h>
    #include <errno.h>
    #include <string.h>
    #include <sys/types.h>
    #include <netinet/in.h>
    #include <sys/socket.h>
    #include <sys/wait.h>
    #include <netdb.h>
    #include <arpa/inet.h>//for inet_ntoa
    #include <unistd.h>//for fork()
    
    #define SERVER_PORT 6666
    #define LISTEN_QUEUE  20
    #define BUFFER_SIZE 1024
    #define FILE_NAME_MAX_SIZE 512
    
    int main() {
        //设置一个socket地址结构server_addr,代表服务器internet地址, 端口
        struct sockaddr_in server_addr;
        bzero(&server_addr, sizeof(server_addr)); //把一段内存区的内容全部设置为0
        server_addr.sin_family = AF_INET;
        server_addr.sin_addr.s_addr = htons(INADDR_ANY);
        server_addr.sin_port = htons(SERVER_PORT);
    
        //创建用于internet的流协议(TCP)socket,用server_socket代表服务器socket
        int server_socket = socket(PF_INET, SOCK_STREAM, 0);
        if (server_socket < 0) {
            printf("Create Socket Failed!");
            exit(1);
        }
    
        //把socket和socket地址结构联系起来
        if (bind(server_socket, (struct sockaddr*) &server_addr,
                sizeof(server_addr))) {
            printf("Server Bind Port: %d Failed!
    ", SERVER_PORT);
            exit(1);
        }
    
        //server_socket用于监听
        if (listen(server_socket, LISTEN_QUEUE)) {
            printf("Server Listen Failed!");
            exit(1);
        }
    
        while (1) {
            //定义客户端的socket地址结构client_addr
            char buffer[BUFFER_SIZE];
            struct sockaddr_in client_addr;
            socklen_t length = sizeof(client_addr);
    
            int client_socket = accept(server_socket,
                    (struct sockaddr*) &client_addr, &length);
            if (client_socket < 0) {
                printf("Server Accept Failed!
    ");
                break;
            }
            bzero(buffer, BUFFER_SIZE);
            // 获取客户端要传输的文件名
            length = recv(client_socket, buffer, BUFFER_SIZE, 0);
            if (length < 0) {
                printf("Server Recieve Data Failed!
    ");
                break;
            }
            char file_name[FILE_NAME_MAX_SIZE + 1];
            bzero(file_name, FILE_NAME_MAX_SIZE + 1);
            strncpy(file_name, buffer,
                    strlen(buffer) > FILE_NAME_MAX_SIZE ?
                            FILE_NAME_MAX_SIZE : strlen(buffer));
            // 新建文件
            FILE * fp = fopen(file_name, "w");
            if (NULL == fp) {
                printf("File: %s CAN NOT WRITE!
    ", file_name);
            } else {
                bzero(buffer, BUFFER_SIZE);
                int file_block_length = 0;
                while ((file_block_length = recv(client_socket, buffer, BUFFER_SIZE,
                        0)) > 0) {
                    if (file_block_length < 0) {
                        printf("Recieve Data From Client Failed!
    ");
                        break;
                    }
                    int write_length = fwrite(buffer, sizeof(char),
                            file_block_length, fp);
                    if (write_length < file_block_length) {
                        printf("File: %s Write Failed
    ", file_name);
                        break;
                    }
                    bzero(buffer, BUFFER_SIZE);
                }
                fclose(fp);
                printf("File: %s Transfer Finished
    
    ", file_name);
            }
            close(client_socket);
        }
        close(server_socket);
        return 0;
    }
    View Code

    客户端

    #include <stdio.h>
    #include <stdlib.h>
    #include <errno.h>
    #include <string.h>
    #include <sys/types.h>
    #include <netinet/in.h>
    #include <sys/socket.h>
    #include <sys/wait.h>
    #include <netdb.h>
    #include <arpa/inet.h>//for inet_ntoa
    #include <unistd.h>//for fork()
    
    #define SERVER_PORT 6666
    #define BUFFER_SIZE 1024
    #define FILE_NAME_MAX_SIZE 512
    
    int main() {
        //设置一个socket地址结构client_addr,代表客户机internet地址, 端口
        struct sockaddr_in client_addr;
        bzero(&client_addr, sizeof(client_addr)); //把一段内存区的内容全部设置为0
        client_addr.sin_family = AF_INET; //internet协议族
        client_addr.sin_addr.s_addr = htons(INADDR_ANY); //INADDR_ANY表示自动获取本机地址
        client_addr.sin_port = htons(0); //0表示让系统自动分配一个空闲端口
        //创建用于internet的流协议(TCP)socket,用client_socket代表客户机socket
        int client_socket = socket(AF_INET, SOCK_STREAM, 0);
        if (client_socket < 0) {
            printf("Create Socket Failed!
    ");
            exit(1);
        }
        //把客户机的socket和客户机的socket地址结构联系起来
        if (bind(client_socket, (struct sockaddr*) &client_addr,
                sizeof(client_addr))) {
            printf("Client Bind Port Failed!
    ");
            exit(1);
        }
    
        //设置一个socket地址结构server_addr,代表服务器的internet地址, 端口
        struct sockaddr_in server_addr;
        bzero(&server_addr, sizeof(server_addr));
        server_addr.sin_family = AF_INET;
        server_addr.sin_port = htons(SERVER_PORT);
        socklen_t server_addr_length = sizeof(server_addr);
        // 向服务器发起连接,连接成功后client_socket代表了客户机和服务器的一个socket连接
        if (connect(client_socket, (struct sockaddr*) &server_addr,
                server_addr_length) < 0) {
            exit(1);
        }
    
        // 连接上服务器, 选择要上传的文件
        char file_name[FILE_NAME_MAX_SIZE + 1];
        bzero(file_name, FILE_NAME_MAX_SIZE + 1);
        printf("Please Input File Name Upload To Server: ");
        scanf("%s", file_name);
    
        char buffer[BUFFER_SIZE];
        bzero(buffer, BUFFER_SIZE);
        strncpy(buffer, file_name,
                strlen(file_name) > BUFFER_SIZE ? BUFFER_SIZE : strlen(file_name));
    
        FILE * fp = fopen(file_name, "r");
        if (NULL == fp) {
            printf("File: %s NOT FOUND! 
    ", file_name);
            exit(1);
        }
    
        // 发送文件名
        int nameLength = send(client_socket, buffer, BUFFER_SIZE, 0);
        if (nameLength < 0) {
            printf("File name Error! 
    ");
            exit(0);
        }
    
        bzero(buffer, BUFFER_SIZE);
        int file_block_length = 0;
    
        while ((file_block_length = fread(buffer, sizeof(char), BUFFER_SIZE, fp))
                > 0) {
            if (send(client_socket, buffer, file_block_length, 0) < 0) {
                printf("Send File:	%s Failed
    ", file_name);
                break;
            }
            bzero(buffer, BUFFER_SIZE);
        }
        printf("File:	%s Transfer Finished
    ", file_name);
        fclose(fp);
        close(client_socket);
        return 0;
    }
    View Code

    计算机网络实验:Linux Apache下模拟服务器解析验证HTTP-Get请求并发回网页数据

    HTTP请求格式:
    <request-line>
    <headers>
    <blank line>
    [<request-body>]
    说明:第一行必须是一个请求行(request-line),用来说明请求类型,要访问的资源以及所使用的HTTP版本.
          紧接着是一个首部(header)小节,用来说明服务器要使用的附加信息.
          之后是一个空行.
          再后面可以添加任意的其他数据[称之为主体(body)].
    例1 GET请求:

    GET / HTTP/1.1
    Accept: */*
    Accept-Language: zh-cn
    Accept-Encoding: gzip, deflate
    User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022)
    Host: www.google.cn
    Connection: Keep-Alive
    说明:请求的第一部分说明了该请求是一个GET请求.该行的第二部分是一个斜杠(/),用来说明请求的是该域名的根目录.该行的最后一部分说明使用的是HTTP1.1版本(另一个可选荐是1.0).
          第2行是请求的第一个首部,HOST将指出请求的目的地.User-Agent,服务器端和客户端脚本都能访问它,它是浏览器类型检测逻辑的重要基础.该信息由你的浏览器来定义,并且在每个请求中自动发送.Connection,通常将浏览器操作设置为Keep-Alive
          第三部分,空行,即使不存在请求主体,这个空行也是必需的.

    例2 POST请求:

    POST / HTTP1.1
    Host:www.wrox.com
    User-Agent:Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022)
    Content-Type:application/x-www-form-urlencoded
    Content-Length:40
    Connection: Keep-Alive

    name=Professional%20Ajax&publisher=Wiley
    说明:请求行开始处的GET改为POST,以表示不同的请求类型.
          Content-Type说明了请求主体的内容是如何编码的.浏览器始终以application/x-www-form-urlencoded的格式编码来传送数据,这是针对简单URL编码的MIME类型.Content-Length说明了请求主体的字节数.
          最后请求主体.名称-值对的形式.

    HTTP响应格式:
    <status-line>

    <headers>
    <blank line>
    [<response-body>]

    HTTP/1.1 200 OK
    Date: Fri, 22 May 2009 06:07:21 GMT
    Content-Type: text/html; charset=UTF-8

    <html>
          <head></head>
          <body>
                <!--body goes here-->
          </body>
    </html>
    说明:HTTP状态码200,找到资源,并且一切正常.
          Date:生成响应的日期和时间.
          Content-Type:指定了MIME类型的HTML(text/html),编码类型是UTF-8
          HTML源文体.

     

     

    程序代码与简要分析

    数据包分析:得到请求的文件,用户名与密码
    int
    analysis_packet(char *packet, char *file, char *ID, char *psw)
    HTTP数据包头 202根据时间与数据包内容大小构建,404返回无法连接
    int
    headerBuilder(char *msg, int status, int size)
    与本机Java写成的运行的ServerSocket连接本机MySQL数据库校验用户名与密码
    bool
    check(char *ID, char *psw)
    主函数,作为服务端监听本机3334端口的数据请求,根据数据包请求校验与分析。

    ServerSocket在本机8081端口监听,为Java与MySQL交互的服务端,对每个Socket请求开辟一个线程处理,返回校验结果。
    #include <cstdio>
    #include <stdlib.h>
    #include <errno.h>
    #include <cstring>
    #include <sys/types.h>
    #include <netinet/in.h>
    #include <sys/socket.h>
    #include <sys/wait.h>
    #include <time.h>
    #include <arpa/inet.h>//for inet_ntoa
    #include <unistd.h>//for fork()
    
    #define MAXN 20480
    
    struct INFO {
        char ID[50], psw[50];
    };
    
    int analysis_packet(char *packet, char *file, char *ID, char *psw) {
        char *p, *q;
        if ((p = strtok(packet, " 
    ")) == NULL)
            return 0;
        if (strcmp(p, "GET") == 0) { //GET
            if ((p = strtok(NULL, " 
    ")) == NULL)
                return 1;
            q = p;
            if ((p = strtok(NULL, " 
    ")) != NULL && strcmp(p, "HTTP/1.1") == 0) { //协议HTTP/1.1
                if ((q = strtok(q, "?")) != NULL) {
                    strcpy(file, q); //文件名
                    if ((q = strtok(NULL, "?")) != NULL) {
                        char *tmp;
                        if ((tmp = strtok(q, "&")) != NULL) {
                            strcpy(ID, tmp + 3);
                        }
                        if ((tmp = strtok(NULL, "&")) != NULL) {
                            strcpy(psw, tmp + 3);
                        }
                        return 2;
                    }
                }
                return 1;
            } else
                printf("未知的协议!
    ");
        } else
            printf("未知的请求!
    ");
        return 0;
    }
    int headerBuilder(char *msg, int status, int size) {
        char num[5];
        char *wday[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
        char *wmon[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug",
                "Sep", "Oct", "Nov", "Dec" };
    
        struct tm *ptr;
        time_t it;
        time(&it);
        ptr = localtime(&it); //取得本地时间
        switch (status) {
        case 200:
            //状态行
            strcat(msg, "HTTP/1.1 200 OK
    ");
            //首部
            strcat(msg, "Date: ");
            strcat(msg, wday[ptr->tm_wday]);
            strcat(msg, ", ");
            sprintf(num, "%d", ptr->tm_mday);
            strcat(msg, num);
            strcat(msg, " ");
            strcat(msg, wmon[ptr->tm_mon]);
            strcat(msg, " ");
            sprintf(num, "%d", 1900 + ptr->tm_year);
            strcat(msg, num);
            strcat(msg, " ");
            sprintf(num, "%d", ptr->tm_hour);
            strcat(msg, num);
            strcat(msg, ":");
            sprintf(num, "%d", ptr->tm_min);
            strcat(msg, num);
            strcat(msg, ":");
            sprintf(num, "%d", ptr->tm_sec);
            strcat(msg, num);
            strcat(msg, " GMT
    ");
            strcat(msg, "Content-Type: text/html;charset=utf-8
    ");
            strcat(msg, "Content-Length: ");
            sprintf(num, "%d", size);
            strcat(msg, num);
            strcat(msg, "
    
    ");
            break;
    
        case 404:
            //状态行
            strcat(msg, "HTTP/1.1 404 Not Found
    ");
            //首部
            strcat(msg, "Date: ");
            strcat(msg, wday[ptr->tm_wday]);
            strcat(msg, ", ");
            sprintf(num, "%d", ptr->tm_mday);
            strcat(msg, num);
            strcat(msg, " ");
            strcat(msg, wmon[ptr->tm_mon]);
            strcat(msg, " ");
            sprintf(num, "%d", 1900 + ptr->tm_year);
            strcat(msg, num);
            strcat(msg, " ");
            sprintf(num, "%d", ptr->tm_hour);
            strcat(msg, num);
            strcat(msg, ":");
            sprintf(num, "%d", ptr->tm_min);
            strcat(msg, num);
            strcat(msg, ":");
            sprintf(num, "%d", ptr->tm_sec);
            strcat(msg, num);
            strcat(msg, " GMT
    
    ");
            break;
        }
        return 0;
    }
    bool check(char *ID, char *psw) {
        printf("check info %s %s
    ", ID, psw);
        int cfd = socket(AF_INET, SOCK_STREAM, 0);
        int recbytes;
        char buffer[50] = { '' };
        struct sockaddr_in s_add;
    
        if (-1 == cfd) {
            printf("socket fail ! 
    ");
            return 0;
        }
        bzero(&s_add, sizeof(struct sockaddr_in));
        s_add.sin_family = AF_INET;
        s_add.sin_addr.s_addr = inet_addr("127.0.0.1");
        s_add.sin_port = htons(8083);
    
        if (connect(cfd, (struct sockaddr *) (&s_add), sizeof(s_add)) == -1) {
            printf("connect fail !
    ");
            return 0;
        }
        printf("connect ok !
    ");
        strcpy(buffer, "login");
        printf("sending %s
    ", buffer);
        if ((recbytes = send(cfd, buffer, 5, 0)) == -1) {
            return 0;
        }
    
        strcpy(buffer, ID);
        buffer[strlen(ID)] = '';
        printf("sending %s
    ", buffer);
        if ((recbytes = send(cfd, buffer, strlen(ID), 0)) == -1) {
            return 0;
        }
        strcpy(buffer, psw);
        buffer[strlen(psw)] = '';
        printf("sending %s
    ", buffer);
        if ((recbytes = send(cfd, buffer, strlen(psw), 0)) == -1) {
            return 0;
        }
        if (-1 == (recbytes = recv(cfd, buffer, 3, 0))) {
            return 0;
        }
        close(cfd);
        printf("receive %s
    ", buffer);
        if (strcmp(buffer, "YES"))
            return 1;
        return 0;
    }
    
    int main() {
        int sockfd, client_fd;
        struct sockaddr_in my_addr;
        struct sockaddr_in remote_addr;
    
        char msg[MAXN] = { '' };
    
        //创建套接字
        if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
            perror("socket create failed!");
            exit(1);
        }
    
        my_addr.sin_family = AF_INET; //通信类型
        my_addr.sin_port = htons(3334); //端口
        my_addr.sin_addr.s_addr = INADDR_ANY; //本地地址
        bzero(&(my_addr.sin_zero), 8);
        //绑定端口地址
        if (bind(sockfd, (struct sockaddr*) &my_addr, sizeof(struct sockaddr))
                == -1) {
            exit(1);
        }
    
        //开始监听
        if (listen(sockfd, 10) == -1) {
            exit(1);
        }
    
        while (1) {
            socklen_t sin_size = sizeof(struct sockaddr_in);
            if ((client_fd = accept(sockfd, (struct sockaddr*) &remote_addr,
                    &sin_size)) == -1) {
                break;
            }
            printf("收到%s的报文
    ", (char*) inet_ntoa(remote_addr.sin_addr));
            //子进程段
            if (!fork()) {
                char buf[MAXN];
                char file[50] = { '' };
                char path[100] = "/Users/pcforsong/ksacm"; //服务器文件的路径
    
                //读报文
                if (read(client_fd, buf, MAXN) < 0) {
                    perror("reading stream error!");
                    continue;
                }
                // printf("%s
    ", buf);
    
                FILE *fp;
                char ID[50] = { '' }, psw[50] = { '' };
                bool checked = false;
                int OP = analysis_packet(buf, file, ID, psw);
                if (OP) { //get
                    //生成本地文件路径
                    strcat(path, file);
                    printf("path: %s
    ", path);
                    printf("info: %s %s
    ", ID, psw);
                    if (!checked && OP == 2 && !check(ID, psw))
                        exit(0);
                    checked = 1;
                    if ((fp = fopen(path, "r")) != NULL) { //找到文件
                        //通过文件指针计算文件大小
                        fseek(fp, 0, SEEK_END); //到文件结尾
                        int size = ftell(fp); //与文件首偏移字节数
                        fseek(fp, 0, SEEK_SET); //移回开头
                        //填写msg添加200响应报文头
                        headerBuilder(msg, 200, size);
                        while (fgets(buf, 2048, fp) != NULL) {
                            strcat(msg, buf);
                        };
                        fclose(fp);
                    } else {
                        //为msg添加404响应报文头
                        headerBuilder(msg, 404, 0);
                    }
                    //向client发送应答报文msg
                    // printf("%s
    ", msg);
                    if (send(client_fd, msg, strlen(msg), 0) == -1)
                        perror("send error!");
                    close(client_fd);
                } else
                    printf("报文解析失败!
    ");
                exit(0);
            }
            close(client_fd);
        }
        return 0;
    }

    附上Windows Socket编程 http://www.cppblog.com/bujiwu/archive/2009/01/11/71707.aspx  

    二,JAVA ServerSocket

    声明一个ServerSocket打开一个未占用的套接口,并开始ServerSocket.aceept 监听端口。

    其他程序可以Socket连接IP与端口,new BufferReader(new InputStreamReader(socket.getInputStream()))接收消息。

    这里使用多线程处理Socket请求,利用继承自Thread类的自定义线程类重载它的run()方法,这样虽然每个Socket都被同样的代码处理,但彼此相互独立,各自拥有各自的资源互不干扰。

    实现多线程有两个办法,一种采用线程类继承Thread来实现。

    另一种做法是实现Runnable接口,通过 new Thread(Runnable的子类实例) 来实现多线程。要提一句,Runnable接口可以实现对同一实例的多线程处理,即线程资源共享,是继承Thread不能做到的,所以Runnable接口被广泛使用于多线程程序。 Thread类构造函数可以为Runnable target赋值,并且Thread类的run方法就是调用target.run(),所以用Runnable接口定义的run方法是父类的。

    new Thread(new Runnable(){
                public void run(){} // 重载Thread中Runnable target的run方法
    }) {
                public void run(){} //子类重载继承自父类的run方法
    }

    TCP协议

    import java.io.*;
    import java.net.*;
    import java.sql.*;
    import java.util.Vector;
    
    class ServerThread implements Runnable {
        private Socket socket;// 定义套接口
        private BufferedReader in;// 定义输入流
        private PrintWriter out;// 定义输出流
        Connection conn;
        int no;
    
        public ServerThread(Socket s) throws IOException {// 线程构造函数
            socket = s;// 取得传递参数
            in = new BufferedReader(new InputStreamReader(socket.getInputStream()));// 创建输入流
            out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(
                    socket.getOutputStream())), true);// 创建输出流
        }
    
        public void run() {// 重写run方法
            System.out.println("监听Socket中...");
            try {
                Class.forName("com.mysql.jdbc.Driver");// 连接数据库
                conn = DriverManager.getConnection(
                        "jdbc:mysql://localhost:3306/javaicq", "root", "");
                while (true) {
                    String OP = in.readLine();
                    // 停止
                    if (OP.equals("end"))
                        break;
                    // 登录
                    else if (OP.equals("login")) {
                        try {
                            PreparedStatement prepare = conn
                                    .prepareStatement("select nickname,password from icq where icqno=?");// 设定数据库查寻条件
    
                            int g = Integer.parseInt(in.readLine());
                            String passwd = in.readLine().trim();
    
                            prepare.setInt(1, g);// 设定参数
                            ResultSet rs = prepare.executeQuery();// 执行数据库查寻
                            if (!rs.next())
                                out.println("用户不存在");
                            else {// 以下比较输入的号码于密码是否相同
                                String pass = rs.getString("password").trim();
                                if (!passwd.equals(pass)) {
                                    out.println("密码错误");
                                } else {
                                    // 以及注册用户的ip 地址
                                    PreparedStatement online = conn
                                            .prepareStatement("update icq set ip=? where icqno=?");
                                    online.setString(1, socket.getInetAddress()
                                            .getHostAddress());
                                    online.setInt(2, g);
                                    online.executeUpdate();
                                    // set status online
                                    PreparedStatement status = conn
                                            .prepareStatement("update icq set status=1 where icqno=?");
                                    status.setInt(1, g);
                                    status.executeUpdate();
                                    out.println("YES");
                                }
                            }
                            rs.close();
                        } catch (Exception e) {
                            e.printStackTrace();
                            out.println("Error");
                        }
                    }
                    // 新建
                    else if (OP.equals("new")) {
                        try {
                            // 准备接受用户的呢称,密码,email,个人资料,籍贯,头像等信息
                            String nickname = in.readLine().trim();
                            String password = in.readLine().trim();
                            String email = in.readLine().trim();
                            String info = in.readLine().trim();
                            String place = in.readLine().trim();
                            int picindex = Integer.parseInt(in.readLine());
    
                            PreparedStatement userInfo = conn
                                    .prepareStatement("insert into icq(nickname,password,email,info,place,pic,icqno) values(?,?,?,?,?,?,?)");
                            userInfo.setString(1, nickname);
                            userInfo.setString(2, password);
                            userInfo.setString(3, email);
                            userInfo.setString(4, info);
                            userInfo.setString(5, place);
                            userInfo.setInt(6, picindex);
                            userInfo.setInt(7, Integer.parseInt(Double.toString(
                                    Math.random()).substring(3, 8)));
                            userInfo.executeUpdate();// 执行数据库添加
                            // 查询其注册的号码
                            PreparedStatement qNo = conn
                                    .prepareStatement("select icqno from icq where nickname=?");
                            qNo.setString(1, nickname);
                            ResultSet rs = qNo.executeQuery();
                            while (rs.next()) { // 找到最近注册的号码
                                no = rs.getInt(1);
                            }
                            out.println("YES");
                            out.println(no);
                            rs.close();
                        } catch (Exception e) {
                            e.printStackTrace();
                            out.println("Error");
                        }
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            } catch (SQLException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } finally {
                try {
                    conn.close();
                    socket.close();
                } catch (IOException e) {
                } catch (SQLException e) {
                }
            }
        }
    }
    
    public class Server {// 主服务器类
        public static void main(String args[]) throws IOException {
            ServerSocket so = new ServerSocket(8081);// 在8081端口创建套接口
            so.setSoTimeout(100000);
            System.out.println("服务器已启动 " + so);
            Socket testServer = new Socket(InetAddress.getLocalHost(), 8081);
            PrintWriter out = new PrintWriter(new BufferedWriter(
                    new OutputStreamWriter(testServer.getOutputStream())), true);
            try {
                while (true) {
                    Socket socket = so.accept();// 无限监听客户的请求
                    System.out.println("建立Socket连接:" + socket);
                    try {
    //                    out.println("login");
    //                    out.println(123);
    //                    out.println(123);
                          ServerThread st = new ServerThread(socket);// 创建新线程处理Socket
                  new Thread(st).start(); 
                    } catch (IOException e) {
                        socket.close();
                    }
                }
            } catch (SocketTimeoutException e) {
                System.out.println("超时断开连接");
            } finally {
                so.close();
            }
        }
    }
    View Code

    UDP协议

    // 创建UDP Socket
    try {
                DatagramSocket sendSocket = new DatagramSocket();
                DatagramSocket receiveSocket = new DatagramSocket(udpPORT);
                System.out.println("创建UDP数据报成功");
            } catch (SocketException se) {
                se.printStackTrace();
                System.out.println("创建UDP数据报失败");
            }
    // 接收数据报
                    byte array[] = new byte[512];
                    for (int x = 0; x < 512; x++)
                        array[x] = ' ';
                    DatagramPacket  receivePacket = new DatagramPacket(array, array.length);
                    receiveSocket.receive(receivePacket);
                    byte[] data = receivePacket.getData();
                    String infofromip = receivePacket.getAddress().getHostAddress()
                            .toString().trim();
                    received = new String(data, 0, data.length);
    // 发送数据报
    String info = "offline";
                    byte[] data = info.getBytes();
                    sendPacket = new DatagramPacket(data, s.length(),
                                InetAddress
                                        .getByName(whoaddmesip.get(i).toString()),
                                sendPort);
                    sendSocket.send(sendPacket);

    二,OpenSSL编程

     http://www.cnblogs.com/LittleHann/p/3741907.html

    三,WebSocket

     WebSocket是为解决客户端与服务端实时通信而产生的技术。其本质是先通过HTTP/HTTPS协议进行握手后创建一个用于交换数据的TCP连接,此后服务端与客户端通过此TCP连接进行实时通信。

    WebSocket规范当前还没有正式版本,草案变化也较为迅速。Tomcat7(本文中的例程来自7.0.42)当前支持RFC 6455定义的WebSocket,而RFC 6455目前还未冻结,将来可能会修复一些Bug,甚至协议本身也可能会产生一些变化。

    RFC6455定义的WebSocket协议由握手和数据传输两个部分组成。

    来自客户端的握手信息类似如下:

    1. GET /chat HTTP/1.1  
    2. Host: server.example.com  
    3. Upgrade: websocket  
    4. Connection: Upgrade  
    5. Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==  
    6. Origin: http://example.com  
    7. Sec-WebSocket-Protocol: chat, superchat  
    8. Sec-WebSocket-Version: 13  


    服务端的握手信息类似如下:

     
    1. HTTP/1.1 101 Switching Protocols  
    2. Upgrade: websocket  
    3. Connection: Upgrade  
    4. Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=  
    5. Sec-WebSocket-Protocol: chat  


    一旦客户端和服务端都发送了握手信息并且成功握手,则数据传输部分将开始。数据传输对客户端和服务端而言都是一个双工通信通道,客户端和服务端来回传递的数据称之为“消息”。

    客户端通过WebSocket URI发起WebSocket连接,WebSocket URIs模式定义如下:

      ws-URI = "ws:" "//" host [ ":" port ] path [ "?" query ]  

      wss-URI = "wss:" "//" host [ ":" port ] path [ "?" query ]  

      ws是普通的WebSocket通信协议,而wss是安全的WebSocket通信协议(就像HTTP与HTTPS之间的差异一样)。在缺省情况下,ws的端口是80而wss的端口是443。

      关于WebSocke协议规范的完整详尽说明,请参考RFC 6455。

      Tomcat7提供的与WebSocket相关的类均位于包org.apache.catalina.websocket之中(包org.apache.catalina.websocket的实现包含于文件catalina.jar之中),它包含有类Constants、MessageInbound、StreamInbound、WebSocketServlet、WsFrame、WsHttpServletRequestWrapper、WsInputStream、WsOutbound。这些类的关系如图 1所示。

                                                                     图1

            包org.apache.catalina.websocket中的这些类为WebSocket开发服务端提供了支持,这些类的主要功能简述如下:

            Constants:包org.apache.catalina.websocket中用到的常数定义在这个类中,它只包含静态常数定义,无任何逻辑实现。

            MessageInbound:基于消息的WebSocket实现类(带内消息),应用程序应当扩展这个类并实现其抽象方法onBinaryMessage和onTextMessage。

           StreamInbound:基于流的WebSocket实现类(带内流),应用程序应当扩展这个类并实现其抽象方法onBinaryData和onTextData。

           WebSocketServlet:提供遵循RFC6455的WebSocket连接的Servlet基本实现。客户端使用WebSocket连接服务端时,需要将WebSocketServlet的子类作为连接入口。同时,该子类应当实现WebSocketServlet的抽象方法createWebSocketInbound,以便创建一个inbound实例(MessageInbound或StreamInbound)。

           WsFrame:代表完整的WebSocket框架。

           WsHttpServletRequestWrapper:包装过的HttpServletRequest对象。

           WsInputStream:基于WebSocket框架底层的socket的输入流。

           WsOutbound:提供发送消息到客户端的功能。它提供的所有向客户端的写方法都是同步的,可以防止多线程同时向客户端写入数据。

    Tomcat是怎样实现WebSocket的
    1)要开始使用WebSocket,你必须继承Tomcat的WebSocket类
    2)编写自己的类,它继承WebSocketServlet类(由于这是一个Servlet,因此必须把它映射到URL)
    3)实现一个消息监听器类,由于它继承自WebSocketServlet类,因此需要自己实现createWebSocketInbound()方法

    此方法能够用于监听事件。有两个必须有的方法:
    一是 protected void onBinaryData(InputStream inStream);
    二是protected void onTextData(Reader reader);

    当WebSocket打开或关闭时,如果你希望收到通知,只需简单地重写onOpen()方法和onClose()方法。

    把数据写到客户端,必须有StreamInbound实现类,它会引用发送器组件WsOutbound,可以简单地通过调用来取到它:myStreamInbound.getWsOutbound()  

    还可以发送二进制数据writeBinaryData(int b);  

     

    或者发送文本数据到客户端writeTextData(char c);  

    注意:这些方法是互斥的。不要同时调用两种方法,以期待既发送二进制数据,又发送文本数据。

    package com.ibcio;
    
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServletRequest;
    
    import org.apache.catalina.websocket.StreamInbound;
    
    @WebServlet(urlPatterns = { "/message" })
    // 如果要接收浏览器的ws://协议的请求就必须实现WebSocketServlet这个类
    public class WebSocketMessageServlet extends
            org.apache.catalina.websocket.WebSocketServlet {
    
        private static final long serialVersionUID = 1L;
    
        public static int ONLINE_USER_COUNT = 1;
    
        public String getUser(HttpServletRequest request) {
            return (String) request.getSession().getAttribute("user");
        }
    
        // 跟平常Servlet不同的是,需要实现createWebSocketInbound,在这里初始化自定义的WebSocket连接对象
        @Override
        protected StreamInbound createWebSocketInbound(String subProtocol,
                HttpServletRequest request) {
            return new WebSocketMessageInbound(this.getUser(request));
        }
    }

    JS

    var websocket;
    
                //初始话WebSocket
                function initWebSocket() {
                    if (window.WebSocket) {
                        websocket = new WebSocket(encodeURI('ws://localhost:8080/WebSocket/message'));
                        websocket.onopen = function() {
                            //连接成功
                            win.setTitle(title + '&nbsp;&nbsp;(已连接)');
                        }
                        websocket.onerror = function() {
                            //连接失败
                            win.setTitle(title + '&nbsp;&nbsp;(连接发生错误)');
                        }
                        websocket.onclose = function() {
                            //连接断开
                            win.setTitle(title + '&nbsp;&nbsp;(已经断开连接)');
                        }
                        //消息接收
                        websocket.onmessage = function(message) {
                            var message = JSON.parse(message.data);
                            //接收用户发送的消息
                            if (message.type == 'message') {
                                output.receive(message);
                            } else if (message.type == 'get_online_user') {
                                //获取在线用户列表
                                var root = onlineUser.getRootNode();
                                
                            } else if (message.type == 'user_join') {
                                //用户上线
                                    var root = onlineUser.getRootNode();
                                    var user = message.user;
                                    var node = root.createNode({
                                        id : user,
                                        text : user,
                                        iconCls : 'user',
                                        leaf : true
                                    });
                                    root.appendChild(node);
                            } else if (message.type == 'user_leave') {
                                    //用户下线
                                    var root = onlineUser.getRootNode();
                                    var user = message.user;
                                    var node = root.findChild('id',user);
                                    root.removeChild(node);
                            }
                        }
                    }
                };
                //发送消息
                function send() {
                    var message = {};
                    if (websocket != null) {
                        if (input.getValue()) {
                            Ext.apply(message, {
                                        from : user,
                                        content : input.getValue(),
                                        timestamp : new Date().getTime(),
                                        type : 'message'
                                    });
                            websocket.send(JSON.stringify(message));
                            //output.receive(message);
                            input.setValue('');
                        }
                    } else {
                        Ext.Msg.alert('提示', '您已经掉线,无法发送消息!');
                    }
                }
            });
    View Code

    四、Objective-C Socket编程

    iPhone的标准推荐CFNetwork C库编程.但是编程比较烦躁。在其它OS往往用类来封装的对Socket函数的处理。比如MFC的CAsysncSocket.在iphone也有类似于开源项目.cocoa AsyncSocket库, 官方网站:http://code.google.com/p/cocoaasyncsocket/ 它用来简化CFnetwork的调用.

    //建立基于UDP的Socket连接
    -(void)openUDPServer{
        //初始化udp
        AsyncUdpSocket *tempSocket=[[AsyncUdpSocket alloc] initWithDelegate:self];
        self.udpSocket=tempSocket;
        [tempSocket release];
        //绑定端口
        NSError *error = nil;
        [self.udpSocket bindToPort:4333 error:&error];
        
        //发送广播设置
        [self.udpSocket enableBroadcast:YES error:&error];
        
        //加入群里,能接收到群里其他客户端的消息
        [self.udpSocket joinMulticastGroup:@"127.0.0.1" error:&error];
        
           //启动接收线程
        [self.udpSocket receiveWithTimeout:-1 tag:0];
    }
    
    //通过UDP,发送消息
    -(void)sendMassage:(NSString *)message
    {   
        
        NSDate *nowTime = [NSDate date];
        
        NSMutableString *sendString=[NSMutableString stringWithCapacity:100];
        [sendString appendString:message];
        //开始发送
        BOOL res = [self.udpSocket sendData:[sendString dataUsingEncoding:NSUTF8StringEncoding] 
                                     toHost:@"127.0.0.1"
                                       port:4333 
                                withTimeout:-1 
        
                                       tag:0];
        
    
           if (!res) {
            UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"提示"
                                                            message:@"发送失败"
                                                           delegate:self
                                                  cancelButtonTitle:@"取消"
                                                  otherButtonTitles:nil];
            [alert show];
            [alert release];
        }
    }

    其委托方法

    #pragma mark UDP Delegate Methods
    - (BOOL)onUdpSocket:(AsyncUdpSocket *)sock didReceiveData:(NSData *)data withTag:(long)tag fromHost:(NSString *)host port:(UInt16)port
    {   
        
        [self.udpSocket receiveWithTimeout:-1 tag:0];
        NSLog(@"host---->%@",host);
           //接收到数据回调

        NSString *info=[[[NSString alloc] initWithData:data encoding: NSUTF8StringEncoding] autorelease];

        return YES;
    }
    
    - (void)onUdpSocket:(AsyncUdpSocket *)sock didNotSendDataWithTag:(long)tag dueToError:(NSError *)error
    {
        //无法发送时,返回的异常提示信息
    }
    - (void)onUdpSocket:(AsyncUdpSocket *)sock didNotReceiveDataWithTag:(long)tag dueToError:(NSError *)error
    {   
        //无法接收时,返回异常提示信息
    }
  • 相关阅读:
    VS2010运行正常的控制台程序在VS2015中出现乱码的解决方法
    把博客园的博客导出为MovableType的文本格式
    2015年全年总结
    2015年第21本:万万没想到,用理工科思维理解世界
    2015年第20本:零秒思考
    参加2015年TOP100会议的零散笔记
    2015第19本:异类--不一样的成功启示录
    在IntelliJ IDEA14中安装go语言插件
    2015第18本:从0到1,ZERO to ONE, Notes on startups, or how to build the future
    2015第17本:脑力活化术
  • 原文地址:https://www.cnblogs.com/updateofsimon/p/3567022.html
Copyright © 2011-2022 走看看