通讯原理
1)找到对方IP
2)数据发送到对方指定的应用程序上。为了标识这些应用程序,所以给这些网络应用程序都用数字标识。为了方便称呼这些数字,叫做端口,逻辑端口。
3)定义通信规则,这个通讯规则称为协议。W3C组织定义了通用协议TCP/IP。
网络参考模型
我们使用网络编程,主要关注网络层和传输层。
网络参考模型中的每一层都有相对应的协议:
1)网络层主要使用IP协议
2)传输层主要使用TCP、UDP协议
3)应用层使用的是应用层协议,例如HTTTP协议
IP地址对应对象InetAddress
常用方法
static InetAddress getLocalHost(); 获得本机的IP地址对象
static InetAddress getByName(ip地址或者完整域名); 通过域名或者IP地址字符串获得IP地址对象
String getHostName(); 获得IP地址对象对应的主机名
如果没有找到IP地址和主机名的对应关系,则返回IP地址字符串
String getHostAddress(); 获得IP地址对象的IP地址字符串
UDP
特点:
1)将数据及源和目的封装成数据包,不需要连接
2)每个数据包的大小限制在64k内
3)因为无连接,所以是不可靠的协议
4)不需要建立连接,速度快
例如:QQ聊天、视频会议、桌面共享
TCP
特点:
1)建立连接,形成传输数据的通道
2)在连接中进行大数据量传输
3)通过三次握手完成连接,是可靠的协议
4)必须建立连接,效率会稍低
例如:下载数据
Socket编程
Socket就是为网络服务提供的一种机制。
通信两端都有Socket。
网络编程说的就是Socket编程。
数据在两个Socket间进行IO传输。
UDP传输
需求:通过udp传输方式,将一段文字数据发送出去
思路:
1)建立udp socket服务
2)提供数据,并将数据封装到数据包中
3)通过socket服务的发送功能,将数据发送出去
4)关闭资源
程序代码:
public class Udpdemo1 {
public static void main(String[] args) throws IOException {
//创建udp服务,通过DatagramSocket对象
DatagramSocket ds = new DatagramSocket();
//确定要发送的数据,并封装成数据包,DatagramPacket对象
byte[] buf = "hello, i'm coming".getBytes();
DatagramPacket dp = new DatagramPacket(buf, buf.length, InetAddress.getByName("localhost"), 10000);
//通过socket服务,将已有的数据包发送出去,通过send(datagramPacket)
ds.send(dp);
//关闭资源
ds.close();
}
}
定义一个应用程序,用于接收udp协议出生的数据并处理。
思路:
1)定义udp Socket服务
2)定义一个数据包用于存储接收到的字节数据。因为数据包对象有更多功能可以提取字节数据中的不同数据信息。
3)通过Socket的receive方法将接收的数据存入已定义好的数据包中。
4)通过数据包对象的特有功能,将这些不同的数据取出,打印在控制台上
5)关闭资源
程序代码:
<span style="font-size:14px;">class UdpReceive{
public static void main(String[] args) throws IOException {
//创建udp socket服务,绑定监听端口号
DatagramSocket ds = new DatagramSocket(10000);
//定义数据包,用于存储数据
byte[] buf = new byte[1024];
DatagramPacket dp = new DatagramPacket(buf, buf.length);
//通过服务的receive方法将数据存入数据包中
ds.receive(dp);
//通过数据包的方法获取其中的数据
String ip = dp.getAddress().getHostAddress();
String data = new String(dp.getData(), 0, dp.getLength());
//关闭资源
ds.close();
}
}</span>
编写一个聊天程序
1)有接收数据的部分和发数据的部分
2)这两部分需要同时执行,需要用到多线程技术,一个线程控制收,一个线程控制发
因为收和发动作不一样,所以要定义两个run方法
发送端线程
class UdpChatSend implements Runnable{
private DatagramSocket ds;
public UdpChatSend(DatagramSocket ds){
this.ds = ds;
}
@Override
public void run() {
//键盘录入
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String line = null;
try {
while((line=br.readLine())!=null){
if(line.equals("886"))//定义结束标记
break;
byte[] buf = line.getBytes();
DatagramPacket dp = new DatagramPacket(buf, buf.length, InetAddress.getByName("localhost"), 11000);
ds.send(dp);
}
} catch (IOException e) {
e.printStackTrace();
}
ds.close();
}
}
class UdpChatReceive implements Runnable{
private DatagramSocket ds;
public UdpChatReceive(DatagramSocket ds){
this.ds = ds;
}
@Override
public void run() {
try {
while(true){
byte[] buf = new byte[1024];
DatagramPacket dp = new DatagramPacket(buf, buf.length);
ds.receive(dp);//阻塞式方法
String ip = dp.getAddress().getHostAddress();
String data = new String(dp.getData(), 0, dp.getLength());
System.out.println(ip+"---"+data);
}
} catch (IOException e) {
e.printStackTrace();
}
ds.close();
}
}
public class UdpChatDemo {
public static void main(String[] args) {
try {
DatagramSocket dSend = new DatagramSocket();
DatagramSocket dReceive = new DatagramSocket(11000);
new Thread(new UdpChatSend(dSend)).start();
new Thread(new UdpChatReceive(dReceive)).start();
} catch (SocketException e) {
e.printStackTrace();
}
}
}
运行截图
如果对方也有发送端和接收端,就可以进行聊天
TCP传输
1)TCP分客户端和服务端2)客户端对应的对象是Socket,服务端对应的对象是ServerSocket
客户端
Socket对象在建立时,就可以去连接指定主机。
因为tcp面向连接,所以在建立socket服务时,就要有服务端存在,并连接成功。
形成通路后,在该通道进行数据的传输。
步骤:
1)创建Socket服务,并指定要连接的主机和端口。
2)获得Socket输出流
3)向输出流中写入数据
4)关闭客户端
程序代码:
public class TcpDemoSend1 {
public static void main(String[] args) throws IOException {
//创建Socket服务,并指定要连接的主机和端口。
Socket s = new Socket("192.168.1.254", 10000);
//获得Socket输出流,以便向流中写入要发送的数据
OutputStream os = s.getOutputStream();
//向输出流中写入数据
os.write("hello, 服务端".getBytes());
//关闭客户端
s.close();
}
}
服务端
需求:定义端点接收数据并打印在控制台上
步骤:
1)建立服务端的Socket服务,通过ServerSocket对象,并监听一个端口
2)获取连接过来的客户端对象,通过ServerSocket的accept方法,该方法是一个阻塞式方法,没有连接就会等,
3)客户端如果发过来数据,那么服务端要使用对应的客户端对象,并获取到该客户端对象的读取流读取发过来的数据,并打印在控制台上。
4)关闭客户端连接
5)关闭服务端(可选)
程序代码:
<pre name="code" class="java">class TcpDemoReceive{
public static void main(String[] args) throws IOException {
//建立服务端的Socket服务,并监听一个端口
ServerSocket ss = new ServerSocket(10000);
//通过accept方法获取连接过来的客户端对象
Socket s = ss.accept();
String ip = s.getInetAddress().getHostAddress();
System.out.println(ip+"----connected");
//获取客户端发送过来的数据,那么要使用客户端对象的读入流来读取数据
InputStream is = s.getInputStream();
byte[] buf = new byte[1024];
int len = is.read(buf);
System.out.println(new String(buf, 0, len));
//关闭客户端
s.close();
//关闭服务端(可选)
ss.close();
}
}
TCP传输中客户端和服务端的互访
需求:客户端给服务端发送数据,服务端收到后,给客户端发送发送反馈信息。
客户端:
1)建立Socket服务,指定连接主机和端口
2)获取Socket输出流,将数据写入流中,通过网络服务发送到服务端
3)获取Socket输入流,将服务器的反馈数据读取出来
4)关闭资源
程序代码:
客户端
public class TcpDemoSend2 {
public static void main(String[] args) throws IOException {
Socket s = new Socket("localhost", 10000);
OutputStream os = s.getOutputStream();
os.write("你好,服务端".getBytes());
InputStream is = s.getInputStream();
byte[] buf = new byte[1024];
int len = is.read(buf);
System.out.println(new String(buf, 0, len));
s.close();
}
}
服务端
class TcpDemoReceive2{
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(10000);
Socket s = ss.accept();
InputStream is = s.getInputStream();
byte[] buf = new byte[1024];
int len = is.read(buf);
System.out.println(new String(buf, 0, len));
OutputStream os = s.getOutputStream();
os.write("你好,客户端".getBytes());
s.close();
ss.close();
}
}
运行截图:
客户端 服务端
练习:
需求:建立一个文本转换器
客户端给服务器发送文本,服务端会将文本转换成大写再返回给客户端,客户端可以不端的进行文本转换,当客户端输入over时,转换结束。
程序代码:
客户端
public class TcpDemoSend3 {
public static void main(String[] args) throws IOException {
Socket s = new Socket("localhost", 10000);
BufferedReader brKeyboard = new BufferedReader(new InputStreamReader(System.in));
PrintWriter pw = new PrintWriter(s.getOutputStream(), true);
BufferedReader brSocket = new BufferedReader(new InputStreamReader(s.getInputStream()));
String line = null;
while((line=brKeyboard.readLine())!=null){
if(line.equals("over"))
break;
//将数据写入socket输出流中,以换行作为分隔符,并且在写入数据后刷新
pw.println(line);
//读取返回信息并打印
System.out.println(brSocket.readLine());
}
s.close();
}
}
服务端
class TcpDemoReceive3{
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(10000);
Socket s = ss.accept();
BufferedReader brSocket = new BufferedReader(new InputStreamReader(s.getInputStream()));
PrintWriter pw = new PrintWriter(s.getOutputStream(), true);
String line = null;
while((line=brSocket.readLine())!=null){
pw.println(line.toUpperCase());
}
s.close();
ss.close();
}
}
客户端运行截图:
练习2:上传图片
程序代码:
客户端
public class PicUploadClient {
public static void main(String[] args) throws IOException {
Socket s = new Socket("localhost", 10000);
InputStream fileIs = new FileInputStream("e:/1.jpg");
OutputStream os = s.getOutputStream();
InputStream is = s.getInputStream();
int len = 0;
byte[] buf = new byte[1024];
while((len=fileIs.read(buf))!=-1){
os.write(buf, 0, len);
}
//禁用此套接字的输出流,添加结束标记,调用该方法后不能再像输出流中写入数据
s.shutdownOutput();
os.write("hehe".getBytes());
len = is.read(buf);
System.out.println(new String(buf, 0, len));
s.close();
}
}
服务端
class PicUploadServer{
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(10000);
Socket s = ss.accept();
OutputStream os = s.getOutputStream();
InputStream is = s.getInputStream();
OutputStream fileOs = new FileOutputStream("e:/copy.jpg");
byte[] buf = new byte[1024];
int len = 0;
while((len=is.read(buf))!=-1){
fileOs.write(buf, 0, len);
}
os.write("上传成功".getBytes());
s.close();
ss.close();
}
}
并发上传图片:
服务端
单线程的服务端有局限性。当A客户端连接上以后,被服务器获取到,服务端执行具体流程。这时B客户端连接,只有等待。
因为服务端还没有处理完A客户端的请求,还没有循环回来执行下accept方法。所以暂时获取不到B客户端对象。
那么为了可以让多个客户端同时并发访问服务端,那么服务端最后就是让每个客户端封装到一个单独的线程中,这样就可以同时处理多个客户端请求。
程序代码:
服务端
public class PicUploadThread {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(10000);
while(true){
Socket s = ss.accept();
new Thread(new PicUploadThreadServer(s)).start();
}
}
}
class PicUploadThreadServer implements Runnable{
private Socket s;
public PicUploadThreadServer(Socket s){
this.s = s;
}
@Override
public void run() {
try {
int count = 0;
OutputStream os = s.getOutputStream();
InputStream is = s.getInputStream();
String ip = s.getInetAddress().getHostAddress();
System.out.println(ip+"----connected");
File file = new File("e:"+File.separator+ip+"("+count+").jpg");
if(file.exists()){
count++;
}
OutputStream fileOs = new FileOutputStream(file);
byte[] buf = new byte[1024];
int len = 0;
while((len=is.read(buf))!=-1){
fileOs.write(buf, 0, len);
}
os.write("上传成功".getBytes());
fileOs.close();
s.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端
class PicUploadClient2{
public static void main(String[] args) throws IOException {
if(args.length!=1){
System.out.println("请选择一个jpg格式的图片");
return;
}
File file = new File(args[0]);
if(!file.exists()||!file.isFile()){
System.out.println("文件不存在或者不是文件");
return;
}
if(!file.getName().endsWith(".jpg")){
System.out.println("不是jpg格式的文件");
return;
}
if(file.length()>1024*1024*5){
System.out.println("文件太大,请重新选择");
return;
}
Socket s = new Socket("localhost", 10000);
System.out.println(file);
InputStream fileIs = new FileInputStream(file);
OutputStream os = s.getOutputStream();
InputStream is = s.getInputStream();
int len = 0;
byte[] buf = new byte[1024];
while((len=fileIs.read(buf))!=-1){
os.write(buf, 0, len);
}
//禁用此套接字的输出流,添加结束标记,调用该方法后不能再像输出流中写入数据
s.shutdownOutput();
len = is.read(buf);
System.out.println(new String(buf, 0, len));
fileIs.close();
s.close();
}
}
练习:并发登录,验证三次
服务端
public class LoginServer {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(10000);
while(true){
Socket s = ss.accept();
new Thread(new LoginThreadServer(s)).start();
}
}
}
class LoginThreadServer implements Runnable{
private Socket s;
public LoginThreadServer(Socket s){
this.s = s;
}
@Override
public void run() {
try {
String ip = s.getInetAddress().getHostAddress();
System.out.println(ip+"----connected");
BufferedReader brSocket = new BufferedReader(new InputStreamReader(s.getInputStream()));
PrintWriter pw = new PrintWriter(s.getOutputStream(), true);
for(int i=0; i<3; i++){
BufferedReader br = new BufferedReader(new FileReader("e:/database.txt"));
String line = null;
System.out.println("in");
String name = brSocket.readLine();
boolean flag = false;
System.out.println(name);
while((line=br.readLine())!=null){
if(line.equals(name)){
flag = true;
break;
}
}
if(flag){
System.out.println(name+",已登录");
pw.println(name+",登录成功");
break;
}else{
System.out.println(name+"正在尝试登录");
pw.println(name+",用户名不存在");
}
br.close();
}
s.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端
class LoginClient {
public static void main(String[] args) throws IOException {
Socket s = new Socket("localhost", 10000);
BufferedReader brKeyboard = new BufferedReader(new InputStreamReader(System.in));
PrintWriter pw = new PrintWriter(s.getOutputStream(), true);
BufferedReader brSocket = new BufferedReader(new InputStreamReader(s.getInputStream()));
String line = null;
while((line=brKeyboard.readLine())!=null){
//将数据写入socket输出流中,以换行作为分隔符,并且在写入数据后刷新
pw.println(line);
//读取返回信息并打印
System.out.println(brSocket.readLine());
}
for(int i=0; i<3; i++){
line=brKeyboard.readLine();
if(line==null)
break;
pw.println(line);
//读取返回信息并打印
System.out.println(brSocket.readLine());
}
brKeyboard.close();
s.close();
}
}
URL
统一资源定位符
构造方法
new URL(str);
new URL(protocol, host, port, file);
常用方法
String getProtocol(); 获得协议
String getHost();获得主机
String getPort();获得端口号
String getFile();获得带参数的文件路径
String getPath();获得文件路径
String getQuery();获得参数部分
示例代码:
public class URLDemo {
public static void main(String[] args) throws MalformedURLException {
URL url = new URL("http://localhost:8080/myweb/demo.html?name=haha&age=12");
System.out.println("getProtocol :"+url.getProtocol());
System.out.println("getHost :"+url.getHost());
System.out.println("getPort :"+url.getPort());
System.out.println("getFile :"+url.getFile());
System.out.println("getPath :"+url.getPath());
System.out.println("getQuery :"+url.getQuery());
}
}
运行截图:
URLConnection openConnection(); 返回一个URLConnection
对象,它表示到URL
所引用的远程对象的连接。
InputStream getInputStream(); 获得输入流,该输入流中的数据是应用层中的数据
public class URLDemo2 {
public static void main(String[] args) throws IOException {
URL url = new URL("http://localhost:8080/myweb/demo.html?name=haha&age=12");
URLConnection urlconn = url.openConnection();
InputStream is = urlconn.getInputStream();
byte[] buf = new byte[1024];
int len = is.read(buf);
System.out.println(new String(buf, 0, len));
}
}