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())

            {

                //捕获client的socket发来的消息

                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:\t" + dateFormat.format(new Date()) + "\t" + msg);

    }

    

    public
static
void PrintClientMsg(String msg)

    {

        System.out.println("Client:\t" + dateFormat.format(new Date()) + "\t" + 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.连接server的IP:端口

        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();

                //打印在client的console

                PrintConsoleMsg(content);

                //发送给server

                writer.println(content);

            }

        }

    }

    

    public
static
void PrintConsoleMsg(String msg)

    {

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

    }

    

    public
static
void PrintServerMsg(String msg)

    {

        System.out.println("Server:\t" + dateFormat.format(new Date()) + "\t" + 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:\t" + dateFormat.format(new Date()) + "\t" + msg);

    }

    public
static
void PrintClientMsg(String msg, int
port)

    {

        System.out.println("Client " + port + ":\t" + dateFormat.format(new Date()) + "\t" + 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:\t" + dateFormat.format(new Date()) + "\t" + msg);

    }

    public
static
void PrintClientMsg(String msg)

    {

        System.out.println("Client:\t" + dateFormat.format(new Date()) + "\t" + 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\n");

        exit(-1);

    }

    printf("bind success...\n");

    //listen success?

    //waiting for connecting

    //QUEUE means 20 requests is permitted

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

    {

        perror("listen error\n");

        exit(-1);

    }

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

    //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\n");

        exit(-1);

    }

 

    print_time();

    printf(" : connect success\n");

    
 

    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\n") == 0 || strcmp(buffer,"exit") == 0)

        {

            print_time();

            printf("exit...\n");

            break;

        }

        printf("Client\t");

        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\n");

        exit(-1);

    }

    print_time();

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

    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\t");

        print_time();

        fputs(send_buf, stdout);

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

        {

            print_time();

            printf("exit...\n");

            break;

        }

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

        printf("Server\t");

        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

Socket编程入门的更多相关文章

  1. 最基础的Python的socket编程入门教程

    最基础的Python的socket编程入门教程 本文介绍使用Python进行Socket网络编程,假设读者已经具备了基本的网络编程知识和Python的基本语法知识,本文中的代码如果没有说明则都是运行在 ...

  2. python socket编程入门(编写server实例)+send 与sendall的区别与使用方法

    python 编写server的步骤: 1. 第一步是创建socket对象.调用socket构造函数.如: socket = socket.socket( family, type ) family参 ...

  3. Java 简单的 socket 编程入门实战

    这个是给女朋友写的:) 首先需要知道我们每个电脑都可以成为server(服务器) 和 client(客户端) 我们需要使用java来实现客户端与服务器的数据传输 先帖上代码 注意这里两张代码处于两个j ...

  4. 网络编程基础【day09】:socket编程入门(一)

    本节内容 1.OSI七层模型 2.概述 3.关系图 4.代码逻辑图 5.socket概念 一.OSI七层模型 二.概述 socket通常也称作"套接字",用于描述IP地址和端口,是 ...

  5. python socket编程入门(编写server实例)-乾颐堂

    python 编写server的步骤: 1. 第一步是创建socket对象.调用socket构造函数.如: socket = socket.socket( family, type ) family参 ...

  6. php socket编程入门

    服务端 <?php /** * File name server.php * 服务器端代码 * * @author guisu.huang * @since 2012-04-11 * */ // ...

  7. C# socket 编程入门

    http://www.cnblogs.com/chenxizhang/archive/2011/09/10/2172994.html

  8. C# Socket编程入门

    一直没有触及到这一块儿,学习下 在看一个小demo   https://www.cnblogs.com/yy3b2007com/p/7476458.html

  9. 脑残式网络编程入门(二):我们在读写Socket时,究竟在读写什么?

    1.引言 本文接上篇<脑残式网络编程入门(一):跟着动画来学TCP三次握手和四次挥手>,继续脑残式的网络编程知识学习 ^_^. 套接字socket是大多数程序员都非常熟悉的概念,它是计算机 ...

随机推荐

  1. [leetcode]215. Kth Largest Element in an Array 数组中第k大的元素

    Find the kth largest element in an unsorted array. Note that it is the kth largest element in the so ...

  2. PHP下ajax跨域的解决方案之window.name

    原理核心:window对象的name属性是一个很特别的属性,当该window的location变化,然后重新加载,它的name属性可以依然保持不变. 依此原理,我们可以在页面A中用iframe加载其他 ...

  3. AssemblyVersion,AssemblyFileVersion解释以及获取

    简而言之,AssemblyVersion: 是程序集的版本,.NET的CLR用,用于标识出该dll的版本信息,用于定义强名称的版本号: AssemblyFileVersion: 为编译器生成的文件加入 ...

  4. Java NIO系列教程(十)DatagramChannel

    Java NIO系列教程(十)DatagramChannel 转载自并发编程网 – ifeve.com,本文链接地址: Java NIO系列教程(十) Java NIO DatagramChannel

  5. stl string 使用指定的分隔符分割成数个子字符串

    #include <iostream> #include <vector> #include <string> #include <algorithm> ...

  6. 2018.10.20 NOIP模拟 面包(数学期望)

    传送门 把方差的式子拆开. 方差=平方的期望-期望的平方. 显然只用维护点对的个数和总方案数就行了. 利用分步的思想来统计. 要统计覆盖一个矩形(x1,y1,x2,y2)(x1,y1,x2,y2)(x ...

  7. schwarz( 施瓦兹)不等式证明

    证明 如果: 函数 y=ax^2+2bx+c 对任意x >=0 时 y>=0; 函数图象在全部x轴上方,故二次方程判别式 b^2-4ac<=0;(即方程无实数解) 即(2b)^2&l ...

  8. post同步请求

    // http://api.hudong.com/iphonexml.do?type=focus-c     //post请求中url不带请求参数,请求参数在参数HTTPBody中设置, 需要创建可变 ...

  9. MySQL性能调优与架构设计——第12章 可扩展设计的基本原则

    第12章 可扩展设计的基本原则 前言: 随着信息量的飞速增加,硬件设备的发展已经慢慢的无法跟上应用系统对处理能力的要求了.此时,我们如何来解决系统对性能的要求?只有一个办法,那就是通过改造系统的架构体 ...

  10. python实现base64算法加密

    python本身有base64加密的模块,不过是用C写的,封装成了.so文件,无法查看源码,本着学习的心态,自己实现了一遍,算法 原理参考 浅谈Base64编码算法. 代码如下: # coding:u ...