java网络编程精解学习笔记(第二章 socket详解)

来源:互联网 发布:网络直播议论文 编辑:程序博客网 时间:2024/06/05 03:20
1 构造socket
socket有几个重载的构造方法:
Socket()
Socket(InetAddress address, int port) //第一个参数表示主机的IP地址
Socket(InetAddress address, int port, InetAddress localAddress, int loaclPort) //第一个地址表示主机的名字
Socket(String host, int port)
Socket(String host, int port, String localHost, int localPort)
除第一个没有参数的构造方法外,其他几个都会尝试与服务器进行连接,连接成功后返回Socket。
以下是扫描服务器端口是否有服务在监听的程序,如果成功建立socket则说明有服务监听在该端口,否则没有:

publicclass ScanPort {


publicstaticvoid main(String[] argsthrows UnknownHostException, IOException {

Socket socket = null;

for(inti=1; i<1030; i++) {

try {

socket = new Socket("localhost"i);

System.out.println("there is a server on this port:" + i);

}catch(Exception e) {

System.out.println("can not connection to port:" + i);

}finally {

if(socket != null) {

socket.close();

}

}

}

}

}

1.1 设置超时时间

Socket socket = new Socket();

SocketAddress address = new InetSocketAddress("localhost", 1000);

socket.connect(address, 1000);

设置一个超时时间1000毫秒,如果1000毫秒后没有连接成功,有其他异常返回其他异常,没有其他异常返回SocketTimeoutException。如果设置为0则一直等待永不超时。

1.2 设置服务器地址:

Socket(InetAddress address, int port) //第一个参数表示主机的IP地址

Socket(String host, int port) //第一个地址表示主机的名字

InetAddress提供了一些静态工厂方法,获取IP地址:

InetAddress inetAddr = InetAddress.getLocalHost();

InetAddress addr1 = InetAddress.getByName("127.0.0.1");

InetAddress addr2 = InetAddress.getByName("www.baidu.com);

1.3 设置客户端地址:

默认情况下,客户端的IP地址来自于客户端主机,端口由客户端随机指定。但是也可以显示指定客户端的IP和port:

Socket(InetAddress address, int port, InetAddress localAddress, int loaclPort)

Socket(String host, int port, String localHost, int localPort)

如果一台主机属于两个网络,则这个主机有两个IP,我们可以手动指定客户端地址与服务器连接:

InetAddress remoteAddr = InetAddress.getByName("192.168.1.1");

InetAddress localAddr = InetAddress.getByName("192.168.1.2");

Socket socket = new Socket(remoteAddr, 8000, localAddr, 5000);

1.4 客户端连接可能抛出的异常:

例程:publicclass ConnectionTest {


publicstaticvoid main(String[] args) {

String host = "localhost";

intport = 0;

if(args.length > 1) {

host = args[0];

port = Integer.parseInt(args[1]);

}

new ConnectionTest().connection(hostport);

}

publicvoid connection(String hostintport) {

Socket socket = null;

String result = "";

try {

SocketAddress addr = new InetSocketAddress(hostport);

longstart = System.currentTimeMillis();

socket = new Socket();

socket.connect(addr, 5000);

longend = System.currentTimeMillis();

result = (end - start) + "ms";

}catch(BindException e) {

result = "bindException";

}catch(UnknownHostException e) {

result = "UnknowHostException";

}catch(ConnectException e) {

result = "connectionException";

}catch(SocketTimeoutException e) {

result = "socket timeout exception";

}catch(IOException e) {

result = "io exception";

}finally {

if(socket != null) {

try {

socket.close();

catch (IOException e) {

e.printStackTrace();

}

}

}

System.out.println(result);

}

}

a>UnknowHostException:

如果入法识别主机的名字或IP地址,会抛出此异常。

b>connectionException:

1>没有监听在该端口的进程时,会报该异常

2>有进程监听在该进程,但是进程处于休眠状态并且队列已经耗尽,则会抛出此异常。例程如下:

server:

publicclass SimpleServer {

publicstaticvoid main(String[] argsthrows Exception {

ServerSocket ss = new ServerSocket(8000, 2);

Thread.sleep(10000);

}

}

clinet:

publicclass SimpleClinet {

publicstaticvoid main(String[] argsthrows UnknownHostException, IOException {

Socket socket1 = new Socket("localhost", 8000);

System.out.println("第一次连接");

Socket socket2 = new Socket("localhost", 8000);

System.out.println("第二次连接");

Socket socket3 = new Socket("localhost", 8000);

System.out.println("第三次连接");

}

}

3>SocketTimeoutException:

如果客户端连接超时,则抛出该异常。socket.connect(addr, 5000); 该方法的第二个参数指定超时时间。

4>BindException:

如果无法将socket对象与本地的IP地址或者端口绑定,则会抛出该异常。例如本地的IP地址指定错误或者端口已经被占用。

socket = new Socket(hostport, InetAddress.getByName("222.11.11.11"), 8888);


2.获取Socket信息:

getInetAddress():获取远程服务器IP地址

getPort():获取远程服务器port

getLocalAddress():获取客户本地IP地址

getLocalPort():获取客户本地端口

getInputStream():获取输入流,从socket对象读取数据。如果socket没有连接或者已经关闭,或者通过shutdownInput()方法关闭输入流,那么抛出IOException异常。

getOutputStream():获取输出流,向socket对象写入数据。如果socket对象没有连接或者已经关闭,或者通过shutdownOutputstream()关闭输出流,那么抛出IOException异常。

如下例程,用Socket发送了一个符合Http协议的请求,用于获取数据:

publicclass HttpClient {


private String host="www.javathinker.org";

privateintport=80;

private Socket socket;

publicvoid createSocket() throws UnknownHostException, IOException {

socket = new Socket(hostport);

}

publicvoid communicate() throws IOException {

StringBuilder sb = new StringBuilder("GET"+"/index.jsp"+"HTTP/1.1\r\n");

sb.append("Host: www.javathinker.org");

sb.append("Accept: */*\r\n");

sb.append("Accept-Language: zh-cn\r\n");

sb.append("Accept-Encoding: gzip,default\r\n");

sb.append("User-Agent: Mozilla/4.0\r\n");

sb.append("Connection: Keep-Alive\r\n\r\n");

//发送http请求

OutputStream os = socket.getOutputStream();

os.write(sb.toString().getBytes());

socket.shutdownOutput();

//响应http请求

InputStream is = socket.getInputStream();

ByteArrayOutputStream baos = new ByteArrayOutputStream();

byte[] buff = newbyte[1024];

intlen;

while((len = is.read()) != -1) {

baos.write(buff, 0, len);

}

System.out.println(new String(baos.toByteArray()));

socket.close();

}

publicstaticvoid main(String[] argsthrows UnknownHostException, IOException {

HttpClient client = new HttpClient();

client.createSocket();

client.communicate();

}

}

如果获取页面的数据量比较大,用ByteArrayOutputStream接受太耗内存,建议以下方法:

BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));

String tmp;

while((tmp = br.readLine()) != null) {

System.out.println(tmp);

}


3关闭Socket:

当客户端与服务器端通信结束后,应该及时关闭socket连接,以释放socket占用的端口等资源。一旦socket被关闭,就不能通过socket进行IO操作,否则会抛出IOException。强烈推荐socket在finally里关闭,已确保socket一定会关闭:

try {

socket = new Socket(hostport);

catch (UnknownHostException e) {

e.printStackTrace();

catch (IOException e) {

e.printStackTrace();

finally {

if(socket != null)

try {

socket.close();

catch (IOException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

socket有几个测试方法:

isClosed():判断socket是否关闭。

isConnected():判断socket连接到远程主机,则返回true;

isBound():如果socket已经与一个端口绑定,则返回true

判断socket是否处于连接状态:socket.isConnected() && !socket.isClosed()


4 半关闭socket:

假设进程A与B进行通信,A发送数据,B接收收据,B怎么确定A数据发送完了呢,有如下几种方法:

a>如果A与B之间传输的字符流时,并且都是一行一行读取数据时,可以约定一个字符串如”bye”,如果B读取到此字符串则停止读取数据。

b>A给B发送一条消息,告诉自己发送的内容的长度。然后A发送正文内容。B读取到该长度的字符或字节时就结束。

c>A发送完数据后,执行close()方法。B继续执行输入流的read()时会返回-1,或者使用bufferedReader执行readLine()时返回null,则说明读到了数据末尾。

d>当执行close()时会输出流和输入流都会关闭,如果希望关闭输入流或输出流之一,那么可以使用socket提供的半关闭方法:

shutdownInput()和shutdownOutput()。进程A关闭输入流后,进程B就会读取到输入流的末尾。需要注意的是:执行了shutdownInput()和shutdownOutput()后并不等同于执行了close()方法。只有执行了close()方法,才会释放socket占用的端口等资源。

socket还提供了两个测试方法:

isInputShutdown()是否半关闭输入流。 isOutputShutdown()是否半关闭输出流。


5.设置Socket的选项:

a>TCP_NODELAY:

设置该选项public void setTcpNoDelay(bookean on)suanfa

读取改选项:public boolean getTcpNoDelay()

默认情况下:发送数据采用Negale算法,negale算法:发送方发送数据时数据不被立即发送到接收方,而是先放到缓存区内。等缓冲区满了在发出。发送一批数据后,等待这批数据回应后在发送下一批。negale算法适用于发送大量数据,并且及时响应的情况。该算法是通过减少传送次数来提高效率的。

如果每次数据发送量很少,并且不需要及时响应则不使用该算法。例如游戏程序鼠标的位置需要实时的发送到服务器端,所以该算法会造成很大的延时。

TCP_NODELAY的默认值是false,意思是使用Negale算法,可以通过setTcpNoDelay(true)把socket缓冲区关闭,让数据实时发送到接收端。如果socket底层不支持该参数。则getTcpNoDelay()和setTcpNoDelay()会抛出SocketException异常。

b>SO_RESUSEADDR:

设置该选项:public void setResuseAddr(boolean b)

读取该选项:public boolean getResuseAddr()

当接收方调用了close()关闭了socket后,如果网络上还有数据没有接收完毕,则底层的socket不会立即释放绑定的端口,它要等到数据接收完全后在释放端口。接收到的数据不会做任何处理。接收剩余数据的目的是防止绑定到该端口的新进程接收到这些数据。

客户端进程一般采用随机分配端口的方式。而服务器端使用固定端口的方式。当服务器程序关闭后,它的端口还会占用一段时间,此时如果重新启动程序,则会由于端口无法绑定而重启失败。为了使进程关闭后即使端口没有立即释放,新程序也能立即绑定到该端口,可以通过设置setResuseAddr(true)来实现。

值得注意的是setResuseAddr(true)需要在绑定到第一个端口之前调用才能生效,例如

Socket socket = new Socket(); //此时服务器没有绑定本地端口,并且没有连接到远程服务器。

socket.setResuseAddr(true);

SocketAddr addr = new InetSocketAddr("remoteAddr", 9000);

socket.connect(addr); //此时服务器绑定到本地匿名端口,并且连接点远程服务器。

c>SO_TIMEOUT:

设置该选项:public void setSoTimeout(int milliseconds)

读取该选项:public int getSoTimeout();

当通过socket的输入流读取数据时,如果没有数据就会等待。例如:

InputStream is = socket.getInputStream();

byte[] buff = new byte[];

is.read(buff);

此时有几种情况:

1>如果输入流里有1024个字节,则把字节读取到buff里,返回读取的字节数

2>如果输入流里没有到末尾,但是不够1024个字节,那么把这些数据读取到buff里,返回读取的自己数。

3>如果输入流里已经读到了末尾,那么返回-1

4>如果连接已断开,会抛出SocketException

5>如果设置了setSoTimeout()方法,那么如果超过了设置的时间这回抛出SocketTimeoutException

setSoTimeout()的单位是毫秒,默认是0表示一直等待。该方法需要在读取数据之前设置,另外抛出SocketTimeoutException后,socket并没有断开还可以尝试接收数据。

例程如下:

SendClient.java:

public class SendClient {
    public static void main(String[] args) throws UnknownHostException, IOException, InterruptedException {
        Socket socket = new Socket("localhost", 8000);
        OutputStream os = socket.getOutputStream();
        os.write("hello".getBytes());
        os.write("world".getBytes());
        Thread.sleep(10000);
        socket.close();
    }
}
ReceiveServer.java:
public class ReceiveServer {
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(8000);
        Socket socket = ss.accept();
        socket.setSoTimeout(5000); //设置socket的超时时间
        InputStream is = socket.getInputStream();
        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
        byte[] buff = new byte[1024];
        int len = -1;
        try {
            while((len = is.read(buff)) != -1) { //如果到达超时时间,则read()方法会抛出超时异常。
                buffer.write(buff, 0, len);
            }
        }catch(SocketTimeoutException e) {
            System.out.print("socket time out");
        }
        System.out.print(new String(buffer.toByteArray()));
        socket.close();
        ss.close();
    }
}
d>SO_LINGER: (缓慢消失)
设置该选项:public void setSoLinger(boolean b, int seconds)
读取该选项:public int getSoLinger()
SO_LINGER控制socket的关闭行为,当socket执行close()时方法立即返回,但是底层的socekt没有立刻关闭,它还会存活一段时间直到把剩余的数据发送的接收方后socket关闭,连接断开。
如果执行了setSoLinger(true, 0),当执行close()后方法立即返回,底层的socket会立即关闭,剩余数据被丢弃。
如果执行了setSoLinger(true, 3600),当执行close()后方法进入阻塞状态,只有满足一下条件后方法会返回:
1.剩余数据全部发送完成。方法返回,底层socket关闭连接断开。
2.时间到达3600s,剩余数据被丢弃,方法返回。底层socket关闭连接断开。
但是实验结果与该结论不一致。设置完该项后,socket调用close()没有进入阻塞状态而是立即返回???
例程如下:
simpleClinet:
public class SimpleClinet {
    public static void main(String[] args) throws UnknownHostException, IOException {
        Socket socket = new Socket("localhost", 8000);
        //socket.setSoLinger(true, 0); //socket关闭后,立即关闭底层socket
        socket.setSoLinger(true, 3600); //socket关闭后,不立即关闭底层socket 延迟3600s后关闭
        OutputStream os = socket.getOutputStream();
        os.write("hahaha".getBytes());
        System.out.println("socket 开始关闭");
        long start = System.currentTimeMillis();
        socket.close();
        long end = System.currentTimeMillis();
        System.out.println("socket 关闭花费时间:" + (end - start) + "ms");
    }
}
simpleServer:
public class SimpleServer {
    public static void main(String[] args) throws IOException, InterruptedException {
        ServerSocket ss = new ServerSocket(8000);
        Socket socket = ss.accept();
        Thread.sleep(5000);
        InputStream is = socket.getInputStream();
        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
        byte[] buff = new byte[1024];
        int len = -1;
        try {
            while((len = is.read(buff)) != -1) {
                buffer.write(buff, 0, len);
            }
        }catch(Exception e) {
            e.printStackTrace();
        }
        System.out.println(new String(buffer.toByteArray()));
    }
}
e>SO_RCVBUF:
设置该选项:public void setReceiveBufferSize(int size)
读取该选项: public int getReceiveBufferSize()
SO_RCVBUF表示socket的接收数据缓冲区大小,当发送大块连续的数据(如http, ftp协议的通信)时可以使用较大的缓冲区,减少发送次数来提供性能。而发送小块交互频繁的数据时(如网络游戏,telnet协议的通信)应该使用较小的缓冲区。这种缓冲区的大小原则也使用与SO_SNDBUF。如果底层不支持该选项,则setReceiveBuffer()时会抛出SocketException.
f>SO_SNDBUF:
设置该选项:public void setSendBufferSize(int size)
读取该选项:public int getSendBufferSize()
该选项表示socket的用于发送数据的缓冲区的大小。如果底层不支持setSendBufferSize()则会抛出SocketException()
g>SO_KEEPALIVE()
设置该选项:public void setKeepAlive(boolean b)
读取该选项:public int getKeepAlive()
当该选项为true时,表示底层的tcp实现会监听连接是否有效,如果连接已经空闲2小时(发送端和接收端没有数据传输),本地的tcp就会向远程的socket发送一个数据包,如果没有响应,tcp就会持续向socket发送11分钟数据包,如果还没有响应那么tcp实现就会关闭socket连接。该选项的默认值是false,表示tcp不监听连接的状态,空闲的连接会一直存活而感受不到服务器的崩溃。
h>服务类型选项
邮局里普通邮件和挂号信属于不同的服务,它们的服务质量和成本不同。在网上传输数据也有不同的服务类型,它们分为:低成本, 高可靠性, 最高吞吐量, 最小延时。 这4中服务还可以组合,例如低成本和高可靠性。
设置服务类型:public void setTrafficClass(int trafficClass)
读取服务类型:public int getTrafficClass()
socket使用4个整数表示服务类型:
低成本(0x02), 高可靠性(0x04), 最高吞吐量(0x08), 最小延时(0x10)
i>设置连接时间(成本),延时和带宽(吞吐量)的可靠性
jdk1.5为socket提供了一个setPerformancePreferences(int connectionTime, int latency, int bandwidth)方法。
参数connectionTime: 表示连接时间
参数latency:表示延时时间
参数bandwidth:表示带宽
使用这三个参数的相对值来表示这几个选项的重要性,例如connectionTime:2, latency:1, bandwidth:3。表示带宽最终要,其次是连接时间,最后是延时时间。

6.发送邮件的SMTP客户程序:
SMTP(简单邮件传输协议)是应用层协议,建立在TCP/IP基础之上。SMTP监听在25端口上,在SMTP一次会话中,客户端发送一系列SMTP命令,服务器端给予回应,返回应答码并带有描述。
BASE64是网络上常用的编码方式,它可以把原始的字符序列转化成不易识别的字符序列,有加密的作用。
邮件客户端程序如下:
public class SMTPClient {
    private String mailHost = "smtp.qq.com";
    private int port = 25;

    public static void main(String[] args) throws UnknownHostException, IOException {
        Message msg = new Message(
                "chenguang19893@126.com",
                "1075398375@qq.com",
                "hello",
                "hi, nice to meet you"
                );
        new SMTPClient().sendMail(msg);
    }

    public void sendMail(Message message) throws UnknownHostException, IOException {
        Socket socket = new Socket(mailHost, port);
        BufferedReader br = getReader(socket);
        PrintWriter pw = getWrite(socket);
        String hostname = InetAddress.getLocalHost().getHostName();

        String username = "chenguang19893@126.com";
        String password = "1509";
        username = new Base64().encode(username);
        password = new Base64().encode(password);
        sendAndRecevice(null, br, pw);
        sendAndRecevice("EHLO " + hostname, br, pw);
        sendAndRecevice("AUTH LOGIN", br, pw);
        sendAndRecevice(username, br, pw);
        sendAndRecevice(password, br, pw);
        sendAndRecevice("MAIL FORM:<" + message.form + ">", br, pw);
        sendAndRecevice("RCPT TO:<" + message.to + ">", br, pw);
        sendAndRecevice("DATA", br, pw);
        pw.println(message.data);
        System.out.println("client>" + message.data);
        sendAndRecevice("Subject:" + message.subject, br, pw);
        sendAndRecevice(".", br, pw);
        sendAndRecevice("QUIT", br, pw);
    }

    public BufferedReader getReader(Socket socket) throws IOException {
        InputStream is = socket.getInputStream();
        return new BufferedReader(new InputStreamReader(is));
    }

    public PrintWriter getWrite(Socket socket) throws IOException {
        OutputStream os = socket.getOutputStream();
        return new PrintWriter(os);
    }

    public void sendAndRecevice(String str, BufferedReader br, PrintWriter pw) throws IOException {
        if(str != null) {
            System.out.println("client>" + str);
            pw.write(str);
        }
        String response;
        if((response = br.readLine()) != null) {
            System.out.println("server>" + response);
        }
    }
}

class Message {
    String form;
    String to;
    String subject;
    String content;
    String data;

    public Message(String form, String to, String subject, String content) {
        this.form = form;
        this.to = to;
        this.subject = subject;
        this.content = content;
        this.data = subject + "\r\n" + content;
    }
}
0 0
原创粉丝点击