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. Linux-——grep

    概念介绍 grep (global search regular expression(RE) and print out the line,全面搜索正则表达式并把行打印出来)是一种强大的文本搜索工具 ...

  2. ubuntu下常用操作

    屏幕截图: 可以用ubuntu自带的截图软件:gnome-screenshot. 该工具截图区域并且复制到剪切板命令为  gnome-screenshot -c -a,可以给该命令添加快捷方式,alt ...

  3. 如何看待阿里 AI 每秒制作 8000 张海报?

    看了其他设计老师们的回答,给了我一些启发,于是更新一波. 设计本质上是产品和服务的一部分,如果只站在设计师角度看这问题,免不了会有一种被取代的危机感. 来源:千锋UI ​但如果站在整个产品和服务的角度 ...

  4. Ui设计流行趋势,对颜色的探讨

    设计风向转换的趋势越来越短,在设计圈中,流行设计的跟新换代更是快.在设计时间越来越短的今天,在经理领导不断催促的时下,如何准确的把握当下的流行趋势,如何在设计之初就能定好设计的基调.这对于还是刚入设计 ...

  5. 在Mockplus中,如何做鼠标悬停时菜单下拉的效果?

    了解Mockplus的用户会知道,该原型工具目前并不直接支持鼠标悬停功能.但我经过尝试,发现想用它实现一个鼠标悬停事件并不是什么难事,比如网页设计中很常见的鼠标悬停时菜单下拉的效果,只要换个思路,利用 ...

  6. UI设计:掌握这6点,轻松0到1

    非科班出身能成为UI设计师吗? 答案是肯定的.世上无难事,只怕有心人.只要找对方法.坚持不懈,即便是零基础也能学好UI设计. 那么零基础学习UI设计,需要学习哪些知识?我们要从哪些地方学起?怎么系统学 ...

  7. 13.11.20 jquery 核心 siblings() 获得同类(不包含自己)循环所有,

    jquery 核心1.选择器,2. 创建dom 元素 3. jquery 执行时 4. 延迟执行 5. 循环 6. 计算长度.7.8 获得选择器和所在节点 9. 获得下标 10. 元素存放数据  11 ...

  8. windows10 装linux子系统

    http://blog.csdn.net/Yuxin_Liu/article/details/52347898 试了一下,下载太慢,就没继续用,可以用实验楼这个网来玩玩linux

  9. 2018.10.12 NOIP模拟 数据结构(线段树)

    传送门 sb线段树题居然还卡常. 修改操作直接更新区间最小值和区间标记下传即可. 询问加起来最多5e65e65e6个数. 因此直接询问5e65e65e6次最小值就行了. 代码

  10. 2018.10.01 NOIP模拟 偷书(状压dp)

    传送门 状压dp经典题. 令f[i][j]f[i][j]f[i][j]表示到第i个,第i−k+1i-k+1i−k+1~iii个物品的状态是j时的最大总和. 然后简单维护一下转移就行了. 由于想皮一下果 ...