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

原文地址:https://www.cnblogs.com/sinkinben/p/10580885.html