一篇看懂Socket开发

时间:2022-01-02 23:55:47

Socket【套接字】是什么,对于这个问题,初次接触的开发人员一般以为他只是一个通讯工具。

Socket接口是TCP/IP网络的API,Socket接口定义了许多函数或例程,程序员可以用它们来开发 TCP/IP网络上的应用程序。

Socket 接口是访问 Internet 使用得最广泛的方法。
如果你有一台刚配好TCP/IP协议的主机,其IP地址是202.120.127.201, 此时在另一台主机或同一台主机上执行ftp 202.120.127.201,显然无法建立连接。因"202.120.127.201" 这台主机没有运行FTP服务软件。同样, 在另一台或同一台主机上运行浏览软件 如Netscape,输入"http://202.120.127.201",也无法建立连接。现在,如果在这台主机上运行一个FTP服务软件(该软件将打开一个Socket, 并将其绑定到21端口),再在这台主机上运行一个Web 服务软件(该软件将打开另一个Socket,并将其绑定到80端口)。这样,在另一台主机或同一台主机上执行ftp 202.120.127.201,FTP客户软件将通过21端口来呼叫主机上由FTP 服务软件提供的Socket,与其建立连接并对话。而在netscape中输入"http://202.120.127.201"时,将通过80端口来呼叫主机上由Web服务软件提供的Socket,与其建 立连接并对话。

在Internet上有很多这样的主机,这些主机一般运行了多个服务软件,同时提供几种服务。每种服务都打开一个Socket,并绑定到一个端口上,不同的端口对应于不同的服务。Socket正如其英文原意那样,象一个多孔插座。以至于Socket为什么会被翻译成【套接字】,这只能说一个不懂技术的翻译大白话。

Socket开发其实很简单

用一个java应用程序模拟一个服务器端:

一篇看懂Socket开发一篇看懂Socket开发
package mySocketServer;

import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import org.apache.commons.httpclient.HttpException;


public class StubSocketServer {

    public static void main(String args[]) throws ParseException {

        StubSocketServer manager = new StubSocketServer();
        manager.doListen();
    }

    public void doListen() {
        ServerSocket server = null;
        try {
            server = new ServerSocket(4700);
            while (true) {
                Socket client = server.accept();
                new Thread(new SSocket(client)).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                server.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * <p>
     * Title: 服务器进程
     * </p>
     */
    class SSocket implements Runnable {

        Socket client;

        public SSocket(Socket client) {
            this.client = client;
        }

        public void run() {
            DataInputStream input;
            DataOutputStream output;
            try {
                input = new DataInputStream(client.getInputStream());// 接收对象
                output = new DataOutputStream(client.getOutputStream());// 回执对象
                
                byte[] b = new byte[1024];
                int msg = input.read(b);// 接收报文
                System.out.println("msg===\n" + new String(b, 0, msg, "UTF-8"));
                new Thread().sleep(5000);// 暂停1分钟60000

                if (msg > 0) {
                    output.write("0012600112345600".getBytes("UTF-8"));
                    output.flush();
                }
            } catch (IOException e) {
                e.printStackTrace();
                System.out.println("读取报文没有成功----");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
View Code

 

然后再做一个模拟的客户端,填上你的主机IP和监听的端口号就可以运行了:

一篇看懂Socket开发一篇看懂Socket开发
            Charset charset = Charset.forName("UTF-8");
                String src="send content";
                byte[] bytes = src.getBytes(charset);
        Socket socket = new Socket(serverIP, serverPort);
        byte[] ret = new byte[20];
        try {
            OutputStream os = socket.getOutputStream();
            InputStream is = socket.getInputStream();

            os.write(bytes);
            os.flush();
            is.read(ret);
        } finally {
            socket.close();
        }    
View Code

 

还有一种读写方法就是

writeUTF(str);

str = readUTF();

直接发送和接收字符串,不建议使用

 

以下是某位大神的原理阐述,Server-Client模型程序的开发原理:

服务器,使用ServerSocket监听指定的端口,端口可以随意指定(由于1024以下的端口通常属于保留端口,在一些操作系统中不可以随意使用,所以建议使用大于1024的端口),等待客户连接请求,客户连接后,会话产生;在完成会话后,关闭连接。

客户端,使用Socket对网络上某一个服务器的某一个端口发出连接请求,一旦连接成功,打开会话;会话完成后,关闭Socket。客户端不需要指定打开的端口,通常临时的、动态的分配一个1024以上的端口。

 
Socket接口是TCP/IP网络的API,Socket接口定义了许多函数或例程,程序员可以用它们来开发TCP/IP网络上的应用程序。要学
Internet上的TCP/IP网络编程,必须理解Socket接口。
Socket接口设计者最先是将接口放在Unix操作系统里面的。如果了解Unix系统的输入和输出的话,就很容易了解Socket了。网络的
Socket数据传输是一种特殊的I/O,Socket也是一种文件描述符。Socket也具有一个类似于打开文件的函数调用Socket(),该函数返
回一个整型的Socket描述符,随后的连接建立、数据传输等操作都是通过该Socket实现的。

常用的Socket类型有两种:流
式Socket(SOCK_STREAM)和数据报式Socket(SOCK_DGRAM)。流式是一种面向连接的Socket,针对于面向连接的TCP
服务应用;数据报式Socket是一种无连接的Socket,对应于无连接的UDP服务应用。
Socket建立为了建立Socket,程序可以调用Socket函数,该函数返回一个类似于文件描述符的句柄。socket函数原型为:int
socket(int domain, int type, int
protocol);domain指明所使用的协议族,通常为PF_INET,表示互联网协议族(TCP/IP协议族);type参数指定socket的
类型:SOCK_STREAM
或SOCK_DGRAM,Socket接口还定义了原始Socket(SOCK_RAW),允许程序使用低层协议;protocol通常赋值"0"。
Socket()调用返回一个整型socket描述符,你可以在后面的调用使用它。
Socket描述符是一个指向内部数据结构的指针,它指向描述符表入口。调用Socket函数时,socket执行体将建立一个Socket,实际上"建
立一个Socket"意味着为一个Socket数据结构分配存储空间。
Socket执行体为你管理描述符表。两个网络程序之间的一个网络连接包括五种信息:通信协议、本地协议地址、本地主机端口、远端主机地址和远端协议端
口。Socket数据结构中包含这五种信息。 socket在测量软件中的使用也很广泛

socket深层次理解

Socket编程基本就是listen,accept以及send,write等几个基本的操作。


于网络编程,我们也言必称TCP/IP,似乎其它网络协议已经不存在了。对于TCP/IP,我们还知道TCP和UDP,前者可以保证数据的正确和可靠性,
后者则允许数据丢失。最后,我们还知道,在建立连接前,必须知道对方的IP地址和端口号。除此,普通的程序员就不会知道太多了,很多时候这些知识已经够用
了。最多,写服务程序的时候,会使用多线程来处理并发访问。

我们还知道如下几个事实:

1。一个指定的端口号不能被多个程序共用。比如,如果IIS占用了80端口,那么Apache就不能也用80端口了。

2。很多防火墙只允许特定目标端口的数据包通过。

3。服务程序在listen某个端口并accept某个连接请求后,会生成一个新的socket来对该请求进行处理。

于是,一个困惑了我很久的问题就产生了。如果一个socket创建后并与80端口绑定后,是否就意味着该socket占用了80端口呢?如果是这样的,那么
当其accept一个请求后,生成的新的socket到底使用的是什么端口呢(我一直以为系统会默认给其分配一个空闲的端口号)?如果是一个空闲的端口,
那一定不是80端口了,于是以后的TCP数据包的目标端口就不是80了--防火墙一定会组织其通过的!实际上,我们可以看到,防火墙并没有阻止这样的连
接,而且这是最常见的连接请求和处理方式。我的不解就是,为什么防火墙没有阻止这样的连接?它是如何判定那条连接是因为connet80端口而生成的?是
不是TCP数据包里有什么特别的标志?或者防火墙记住了什么东西?

后来,我又仔细研读了TCP/IP的协议栈的原理,对很多概念有了更深
刻的认识。比如,在TCP和UDP同属于传输层,共同架设在IP层(网络层)之上。而IP层主要负责的是在节点之间(End to
End)的数据包传送,这里的节点是一台网络设备,比如计算机。因为IP层只负责把数据送到节点,而不能区分上面的不同应用,所以TCP和UDP协议在其
基础上加入了端口的信息,端口于是标识的是一个节点上的一个应用。除了增加端口信息,UPD协议基本就没有对IP层的数据进行任何的处理了。而TCP协议
还加入了更加复杂的传输控制,比如滑动的数据发送窗口(Slice
Window),以及接收确认和重发机制,以达到数据的可靠传送。不管应用层看到的是怎样一个稳定的TCP数据流,下面传送的都是一个个的IP数据包,需
要由TCP协议来进行数据重组。

所以,我有理由怀疑,防火墙并没有足够的信息判断TCP数据包的更多信息,除了IP地址和端口号。而且,我们也看到,所谓的端口,是为了区分不同的应用的,以在不同的IP包来到的时候能够正确转发。

TCP/IP只是一个协议栈,就像操作系统的运行机制一样,必须要具体实现,同时还要提供对外的操作接口。就像操作系统会提供标准的编程接口,比如Win32编程接口一样,TCP/IP也必须对外提供编程接口,这就是Socket编程接口--原来是这么回事啊!

在Socket编程接口里,设计者提出了一个很重要的概念,那就是socket。这个socket跟文件句柄很相似,实际上在BSD系统里就是跟文件句柄一
样存放在一样的进程句柄表里。这个socket其实是一个序号,表示其在句柄表中的位置。这一点,我们已经见过很多了,比如文件句柄,窗口句柄等等。这些
句柄,其实是代表了系统中的某些特定的对象,用于在各种函数中作为参数传入,以对特定的对象进行操作--这其实是C语言的问题,在C++语言里,这个句柄
其实就是this指针,实际就是对象指针啦。

现在我们知道,socket跟TCP/IP并没有必然的联系。Socket编程接口在设计的
时候,就希望也能适应其他的网络协议。所以,socket的出现只是可以更方便的使用TCP/IP协议栈而已,其对TCP/IP进行了抽象,形成了几个最
基本的函数接口。比如create,listen,accept,connect,read和write等等。

现在我们明白,如果一个程
序创建了一个socket,并让其监听80端口,其实是向TCP/IP协议栈声明了其对80端口的占有。以后,所有目标是80端口的TCP数据包都会转发
给该程序(这里的程序,因为使用的是Socket编程接口,所以首先由Socket层来处理)。所谓accept函数,其实抽象的是TCP的连接建立过
程。accept函数返回的新socket其实指代的是本次创建的连接,而一个连接是包括两部分信息的,一个是源IP和源端口,另一个是宿IP和宿端口。
所以,accept可以产生多个不同的socket,而这些socket里包含的宿IP和宿端口是不变的,变化的只是源IP和源端口。这样的话,这些
socket宿端口就可以都是80,而Socket层还是能根据源/宿对来准确地分辨出IP包和socket的归属关系,从而完成对TCP/IP协议的操
作封装!而同时,放火墙的对IP包的处理规则也是清晰明了,不存在前面设想的种种复杂的情形。