您的位置:首页 > 理论基础 > 计算机网络

黑马程序员_网络编程概述

2015-07-15 22:42 573 查看


网络基本知识:

在java中网络程序有两种协议:TCP和UDP,TCP通过握手协议进行可靠的连接,UDP则是不可靠连接。

IP地址:用于标记一台计算机的身份证。

IP地址由网络地址(确定网络)和主机地址(网络中的主机)组成。

子网掩码:为了区分网络地址和主机地址。

IP地址分为A类地址、B类地址、C类地址(常用)、D类地址、E类地址。

127.0.0.1(localhost)是本机地址。

IPV4和IPV6

IPV4使用4个十进制数表示,即32位二进制。

SMTP是简单邮件传输协议,端口号是25.

telnet用于连接远程计算机或者因特网计算机提供的服务。每个服务都会设定一个端口。

给出类似 telnet ip port 即可和特定的服务进行通信

如果要连接因特网的服务,不仅要给出端口,还要给出计算机的名称,只有给出IP地址和端口号时,才能够请求服务,并接收到应答。


URL和URI

URI:统一资源标识符,用于标识一个web资源,包含了两个部分。

(1)URL:统一资源定位符。能够精确的定位数据的URI

(2)URN:统一资源名称。除了URL的URI

在java中URI和URL是分开的两个类,URI类专门用于解析,URL用于通信。


URL


1.URI分类

绝对和相对:

(1)绝对URI是指有确定的协议。比如http,ftp。后面以/进行分隔

(2)相对URI是没有scheme的。

透明和不透明:

(1)不透明URI是不能够被解析的URI。不透明URI是绝对URI。scheme后面的部分不是以/进行分割。

分层和不分层:

(1)分层是绝对透明URI或相对URI。

所有的网页端口都是80.


2.URI的作用:


(1)解析

URI的格式:

[scheme:]scheme-specific-part[#fragment]

scheme表示用的协议,可以是http\https\ftp\file等。

scheme-specific-part是其余部分。

进一步细分:

[scheme:][//authority][path][?query][#fragment]

常用方法:

getScheme()获得scheme;
getSchemeSpecificPart()
getPath()
getAuthority()


(2)相对标识符和绝对标识符的转换

resolve和relative函数。

示例代码:

任务1:取得特定网址的html代码。

任务2:分析地址信息。

任务3:绝对地址和相对地址转换

[java] view
plaincopy

package org.core;

import java.net.*;

import java.util.*;

public class URLTest1 {

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

URL url = new URL("http://www.ecnu.edu.cn");

Scanner in = new Scanner(url.openStream());

while (in.hasNextLine()) {

String str = in.nextLine();

System.out.println(str);

}

URI uri = new URI("http://blog.csdn.net/xiazdong");

System.out.println(uri.getScheme());

System.out.println(uri.getSchemeSpecificPart());

System.out.println(uri.getAuthority());

System.out.println(uri.getUserInfo());

System.out.println(uri.getHost());

System.out.println(uri.getPort());

System.out.println(uri.getPath());

System.out.println(uri.getQuery());

System.out.println(uri.getFragment());

String str = "/article/details/6705033";

URI&n
20000
bsp;combined = uri.resolve(str);// 根据uri的路径把str变成绝对地址

System.out.println(combined.getScheme()

+ combined.getSchemeSpecificPart());

URI relative = uri.relativize(new URI(str));

System.out.println(relative.getSchemeSpecificPart());

}

}


URL和URLConnection


URL的作用

1.如果想要获取某个网页的html源代码,比如http://blog.csdn.net/xiazdong 则只需要:

(1)URL url = new URL("http://blog.csdn.net/xiazdong");

(2)Scanner in = new Scanner(url.openStream());

即可.

2.获取消息头信息

URLConnection connection = url.openConnection();
connection.getHeaderFields()返回一个Map<String,List<String>>
connection.getContentLength();
connection.getContentType();
connection.setDoOutput(true)获得输出流
connection.getOutputStream();
connection.getInputStream();

代码示例:

[java] view
plaincopy

package org.core;

import java.net.*;

import sun.misc.*;

import java.util.*;

import java.io.*;

public class URLConnectionTest {

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

String urlName = "http://java.sun.com";

URL url = new URL(urlName);

URLConnection connection = url.openConnection();

Map<String, List<String>> map = connection.getHeaderFields();

for (Map.Entry<String, List<String>> entry : map.entrySet()) {

String key = entry.getKey();

List<String> value = entry.getValue();

System.out.println(key + ":" + value);

}

}

}

在网页中如果要提交数据给web服务器,通常要把数据发送给web服务器,然后web服务器委派一个脚本对数据进行处理,返回一个相应。

通常发送数据的方法有两种:get和post。

(1)get方法是直接把数据跟在url的后面,以name=value进行传输,

每个数据之间用&进行分割,value中的空格用+替换,非字母数字用%替换,并后跟两个16进制数,这种编码方式称为URL编码。URLEncoder和URLDecoder

(2)post方法是通过URLConnection发送给服务器,编码方式和get一样。URLEncoder.encode(VALUE,"UTF-8");

一般在传输中文时会运用编码和解码。

示例:通过URLEncoder和URLDecoder编码和解码




InetAddress 根据域名得到IP地址或名称

没有构造方法,通过:

(1)InetAddress i1 = InetAddress.getByName(String)返回一个InetAddress实例。

(2)如果一个地址有多个ip地址,比如google,有3个ip地址,就调用InetAddress[] i2 = InetAddress.getAllByName(String);

InetAddress.getLocalhost()获得本机的InetAddress实例。

代码实例:

[java] view
plaincopy

package org.core;

import java.net.InetAddress;

public class InetAddressTest {

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

InetAddress local = InetAddress.getLocalHost();

System.out.println("本机地址:"+local.getHostAddress());

System.out.println("本机名称:"+local.getHostName());

InetAddress[] remote = InetAddress.getAllByName("www.google.com");

for(InetAddress a : remote)

{

System.out.println("地址:"+a.getHostAddress());

System.out.println("名称:"+a.getHostName());

}

}

}


Socket(TCP)

Socket是一个用于机器之间通信的类。

Socket客户端:

(1)Socket s = new Socket(ip,port);打开一个套接字,发送请求

(2)InputStream istream = s.getInputStream();接收数据

(3)OutputStream ostream = s.getOutputStream();发送数据

需要用PrintWriter和Scanner进行包装,并且注意PrintWriter的自动缓冲。

Socket服务器:注意多个客户端同时访问服务器的问题:多线程

(1)ServerSocket server = new ServerSocket(port);创建一个端口

(2)Socket s = server.accept(); 只有当有客户端请求并连接,函数才会返回

(3)InputStream istream = s.getInputStream();接收数据

(4)OutputStream ostream = s.getOutputStream();发送数据

需要用PrintWriter和Scanner进行包装,并且注意PrintWriter的自动缓冲。

我们在使用PrintWriter时需要使用println()函数;

当服务器或客户端任意一方请求结束通信,则立刻停止。

问题1:在套接字中会发生阻塞的地方:

(1)实例化Socket时,会阻塞。

(2)在in.nextLine()类似操作时会阻塞。

解决方法:

(1)对于第一个问题,解决方法:

Socket s = new Socket();建立无连接socket
s.connect(new InetSocketAddress(host,port),timeout);设置超时。

(2)对于第二个问题,解决方法是设置s.setSoTimeout(long)设置超时时间

问题2:当客户端想要关闭套接字时,但却不能确定服务器是否还在发送数据,但是只要一关闭就立刻断开。

解决方法:

socket.shutdownOutput()关闭输出流

socket.shutdownInput()关闭输入流

半关闭示例代码:客户端发送hello给服务器,同时关闭输出流,服务器接收到后关闭输入流,等待5秒发送ECHO hello给客户端。

Client:

[java] view
plaincopy

import java.net.*;

import java.io.*;

import java.util.*;

public class shutdownOutputClient {

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

Socket s = new Socket("localhost",8819);

Scanner in = new Scanner(s.getInputStream());

PrintWriter out = new PrintWriter(s.getOutputStream(),true);

out.println("Hello");//输出hello

s.shutdownOutput(); //关闭输出流

System.out.println("关闭连接");

while(in.hasNextLine()){

System.out.println(in.nextLine());

}

s.close();

}

}

Server:

[java] view
plaincopy

import java.net.*;

import java.io.*;

import java.util.*;

public class shutdownOutputServer {

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

ServerSocket server = new ServerSocket(8819);

Socket s = server.accept();

Scanner in = new Scanner(s.getInputStream());

PrintWriter out = new PrintWriter(s.getOutputStream(),true);

String str = in.nextLine();

System.out.println(str);

s.shutdownInput();

System.out.println("关闭输入流");

Thread.sleep(5000);

out.println("Echo:"+str);

s.close();

}

}

综合代码举例:实现一个简单的对等通信程序,通过多线程,一个线程接收数据,一个线程发送数据。

用户1:

[java] view
plaincopy

import java.util.*;

import java.io.*;

import java.net.*;

public class Client{

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

Socket s = new Socket("localhost",8819);

PrintWriter out = new PrintWriter(s.getOutputStream(),true);

Thread t = new Thread(new Receive(s));

t.start();

//以下代码用于发送数据

Scanner in = new Scanner(System.in);//键盘输入

while(in.hasNextLine()){ //一直不断

out.println(in.nextLine()); //发送键盘输入数据

}

}

}

class Receive implements Runnable //这个类用于接收数据

{

private Socket s;

public Receive(Socket s)

{

this.s = s;

}

public void run()

{

try{

Scanner in = new Scanner(s.getInputStream()); //in:接收数据

String str = null;

while(true)

{

str = in.nextLine();

System.out.println("服务器说:"+str); //打印接收数据

}

}

catch(Exception e){}

}

}

用户2:

[java] view
plaincopy

import java.util.*;

import java.io.*;

import java.net.*;

public class Server{

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

ServerSocket server = new ServerSocket(8819);

Socket s = server.accept();

PrintWriter out = new PrintWriter(s.getOutputStream(),true);

Thread t = new Thread(new Receive1(s));

t.start();

//以下代码用于发送数据

Scanner in = new Scanner(System.in);//键盘输入

while(in.hasNextLine()){ //一直不断

out.println(in.nextLine()); //发送键盘输入数据

}

}

}

class Receive1 implements Runnable //这个类用于接收数据

{

private Socket s;

public Receive1(Socket s)

{

this.s = s;

}

public void run()

{

try{

Scanner in = new Scanner(s.getInputStream()); //in:接收数据

String str = null;

while(true)

{

str = in.nextLine();

System.out.println("客户端说:"+str); //打印接收数据

}

}

catch(Exception e){}

}

}

以上的程序属于C/S,需要同时维护客户端和服务器的代码。

B/S:浏览器和服务器,只需要维护一方代码即可。

聊天工具使用UDP非常多,因为我们通常也会遇到我们发给另一个人一条消息,另一个人却没有收到的情况。

DatagramPacket和DatagramSocket 数据报

代码举例:实现服务器发送数据报到客户端。

Client:

[java] view
plaincopy

package org.core;

import java.net.*;

import java.io.*;

public class DatagramClient {

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

byte[]buf = new byte[1024];

DatagramPacket packet = new DatagramPacket(buf,1024);

DatagramSocket client = new DatagramSocket(9000);

client.receive(packet);

String str = new String(buf,0,packet.getLength());

System.out.println(packet.getAddress().getHostName()+":"+str);

client.close();

}

}

Server:

[java] view
plaincopy

package org.core;

import java.net.DatagramPacket;

import java.net.DatagramSocket;

import java.net.InetAddress;

public class DatagramServer {

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

DatagramSocket server = new DatagramSocket(3000);

String str = "hello world";

DatagramPacket packet = new DatagramPacket(str.getBytes(),str.length(),InetAddress.getLocalHost(),9000);

server.send(packet);

server.close();

}

}


QQ聊天应用

Server端

[java] view
plaincopy

package org.xiazdong.server;

import java.awt.BorderLayout;

import java.awt.event.ActionEvent;

import java.awt.event.ActionListener;

import java.io.BufferedReader;

import java.io.InputStreamReader;

import java.io.PrintStream;

import java.net.InetAddress;

import java.net.ServerSocket;

import java.net.Socket;

import javax.swing.JButton;

import javax.swing.JFrame;

import javax.swing.JPanel;

import javax.swing.JScrollPane;

import javax.swing.JTextArea;

import javax.swing.JTextField;

public class Server3 extends JFrame{

static JTextArea area;

JTextField field;

JButton button;

static PrintStream writer;

public Server3(){

this.setTitle("服务器");

this.setSize(400,500);

area = new JTextArea(25,30);

area.setEditable(false);

field = new JTextField(20);

button = new JButton("提交");

JPanel panel = new JPanel();

JScrollPane sp = new JScrollPane(area);

this.add(sp,BorderLayout.CENTER);

panel.add(field);

panel.add(button);

this.add(panel,BorderLayout.SOUTH);

this.setVisible(true);

this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

button.addActionListener(new ActionListener(){

@Override

public void actionPerformed(ActionEvent e) {

String text = field.getText();

writer.println(text);

area.append("我:"+text+"\n");

field.setText("");

}

});

}

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

Server3 s = new Server3();

ServerSocket server = new ServerSocket(8899);

System.out.println("开始监听...");

Socket socket = server.accept();

InetAddress address = socket.getInetAddress();

String name = address.getLocalHost().getHostName();

System.out.println(name+"已连接");

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

writer = new PrintStream(socket.getOutputStream(), true);

while (true) {

String line = null;

line = reader.readLine();

if (line != null) {

area.append("客户端:"+line+"\n");

}

}

}

}

Client端

[java] view
plaincopy

package org.xiazdong.client;

import java.awt.BorderLayout;

import java.awt.event.ActionEvent;

import java.awt.event.ActionListener;

import java.io.BufferedReader;

import java.io.InputStreamReader;

import java.io.OutputStream;

import java.io.PrintStream;

import java.io.PrintWriter;

import java.net.Socket;

import javax.swing.JButton;

import javax.swing.JFrame;

import javax.swing.JPanel;

import javax.swing.JScrollPane;

import javax.swing.JTextArea;

import javax.swing.JTextField;

public class Client3 extends JFrame{

static JTextArea area;

JTextField field;

JButton button;

static PrintWriter writer;

public Client3(){

this.setTitle("客户端");

this.setSize(400,500);

area = new JTextArea(25,30);

area.setEditable(false);

field = new JTextField(20);

button = new JButton("提交");

JScrollPane sp = new JScrollPane(area);

JPanel panel = new JPanel();

this.add(sp,BorderLayout.CENTER);

panel.add(field);

panel.add(button);

this.add(panel,BorderLayout.SOUTH);

this.setVisible(true);

this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

button.addActionListener(new ActionListener(){

@Override

public void actionPerformed(ActionEvent e) {

String text = field.getText();

writer.println(text);

area.append("我:"+text+"\n");

field.setText("");

}

});

}

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

Client3 c = new Client3();

Socket socket = new Socket("127.0.0.1",8899);

OutputStream out = socket.getOutputStream();

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

writer = new PrintWriter(out,true);

System.out.println("已经成功和服务器连接...");

while(true){

String line = reader1.readLine();

area.append("服务器:"+line+"\n");

}

}

}


PrintWriter的autoflush

如果PrintWriter writer = new PrintWriter(out,true);

则调用println()、printf()、format()函数时会自动刷新,其他函数都不会,比如write()、print()函数时不会自动刷新


网络编程常见异常

第1个异常是java.net.BindException:Address already in use: JVM_Bind。该异常发生在服务器端进行new ServerSocket(port)(port是一个0,65536的整型值)操作时。异常的原因是以为与port一样的一个端口已经被启动,并进行监听。此时用netstat –an命令,可以看到一个Listending状态的端口。只需要找一个没有被占用的端口就能解决这个问题。

第2个异常是java.net.ConnectException: Connection refused: connect。该异常发生在客户端进行new Socket(ip, port)操作时,该异常发生的原因是或者具有ip地址的机器不能找到(也就是说从当前机器不存在到指定ip路由),或者是该ip存在,但找不到指定的端口进行监听。出现该问题,首先检查客户端的ip和port是否写错了,如果正确则从客户端ping一下服务器看是否能ping通,如果能ping通(服务服务器端把ping禁掉则需要另外的办法),则看在服务器端的监听指定端口的程序是否启动,这个肯定能解决这个问题。

第3个异常是java.net.SocketException: Socket is closed,该异常在客户端和服务器均可能发生。异常的原因是己方主动关闭了连接后(调用了Socket的close方法)再对网络连接进行读写操作。

第4个异常是java.net.SocketException: (Connection reset或者Connect reset by peer:Socket write error)。该异常在客户端和服务器端均有可能发生,引起该异常的原因有两个,第一个就是如果一端的Socket被关闭(或主动关闭或者因为异常退出而引起的关闭),另一端仍发送数据,发送的第一个数据包引发该异常(Connect reset by peer)。另一个是一端退出,但退出时并未关闭该连接,另一端如果在从连接中读数据则抛出该异常(Connection
reset)。简单的说就是在连接断开后的读和写操作引起的。

第5个异常是java.net.SocketException: Broken pipe。该异常在客户端和服务器均有可能发生。在第4个异常的第一种情况中(也就是抛出SocketExcepton:Connect reset by peer:Socket write error后),如果再继续写数据则抛出该异常。前两个异常的解决方法是首先确保程序退出前关闭所有的网络连接,其次是要检测对方的关闭连接操作,发现对方关闭连接后自己也要关闭该连接。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: