Hi guys, I have a client that would like to check the variations around their website. They have 5 million URLs to check. If I was to send requests/pings synchronously, it would take me 23 days. So I'm looking for a multithreaded solution. I originally started this problem off in Python, but didn't see much improvement/couldn't scale well, so here I am in Java, and if this fails too, I'll try in Go before throwing in the towel.
The issue is I'm not seeing any improvements at all with multithreading. Perhaps I'm implementing it wrong, could anyone please help me?
Edits:
I'll just be making edits here and new comers can look at the history of this post to see how I've progressed through the problem.
This is the socket suggestion, fails when I try to run it in a thread, unsure what I'm doing wrong here too.
Main Class:
package com.company;
import java.io.IOException;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
public class Main extends Thread{
public static void main(String[] args) throws IOException {
long startTime = System.nanoTime();
Helpers.get("www.google.com", 80); // works here
String path = "test.txt";
boolean append = true;
for (int x = 0; x < 1; x++) {
ArrayList<String> urls = new ArrayList<String>();
// when x = 0, y = 0 | 10 /\ when x = 1, y = 10 | 20
for (int y= x * 10;y < ((x + 1) * 10); y++){
urls.add(String.format("www.google%d.com/", y)); // doesn't work here
}
Thread thread = new Thread(new Helpers(path, append, urls, 80));
thread.start();
thread.interrupt();
}
long endTime = System.nanoTime();
long duration = TimeUnit.NANOSECONDS.toMillis(endTime - startTime);
System.out.println(duration + " ms");
}
}
Helpers Class:
package com.company;
import java.io.IOException;
import java.net.*;import java.io.FileWriter;
import java.io.PrintWriter;import java.util.ArrayList;
public class Helpers extends Thread{
public Helpers(String path, boolean append, ArrayList<String> urls, int port) throws IOException {
this.run(path, append, urls, port);
}
public void run(String path, boolean append, ArrayList<String> urls, int port) throws IOException {
for (String url : urls) {
String status = Helpers.get(url, port);Helpers.writeToFile(path, append, status);
System.out.println(status);
}
}
public static String get(String url, int port) throws IOException {
try {
Socket conn = new Socket(url, 80);
conn.close();
return url + " | Success";
}catch (UnknownHostException error){return url + " | Failed";
}
}
You are implementing it wrong. You should make Helpers class extend thread or implement runnable. Pass everything you need to this class, for example, url, file pointer etc.
In your main class, create object of Helper and then run it as a thread.
You can try a different approach. Instead of making HTTPConnection for every call, you can try creating a socket connection to the webserver and then make multiple calls (GET/HEAD) to different URLs.
/**
* hostname of the webserver e.g. www.w3.org
* #param hostname
* #param urlList
* #throws IOException
*/
public static void makingHTTPCall( String hostname , List<String> urlList) throws IOException {
SSLSocketFactory factory = (SSLSocketFactory)SSLSocketFactory.getDefault();
SSLSocket socket =
(SSLSocket)factory.createSocket(hostname, 443);
BufferedReader in
= new BufferedReader(
new InputStreamReader(socket.getInputStream()));
PrintWriter out
= new PrintWriter(socket.getOutputStream(), true);
/**
* if required create different url List and pass those list to separate thread for better performance
*/
urlList.forEach(
url -> {
System.out.println("Making call to url /" + url);
out.println("HEAD " + url + " HTTP/1.1\r\n");
out.flush();
String line = "";
try {
while ((line = in.readLine()) != null) {
System.out.println("Response" + line);
break;
}
} catch (IOException e) {
e.printStackTrace();
}
}
);
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
out.close();
}
I have tried with the SSL socket you can change this according to your configuration.
Related
So I have a client written in Java that i want to use to test out sending email but instead of using an already existing SMTP like google, i want to have my own local server to test out sending mock emails between two mock emails.
I've been trying to look all over the internet for good sources on how to code a simple SMTP Server but i've had zero luck.
I do have a basic server code that when i run it, i can connect my Client to it but at the moment it won't handle any email functionality.
TCPServer.java
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.io.*;
import java.net.*;
public class TCPServer{
private ServerSocket server;
/**
* The TCPServer constructor initiate the socket
* #param ipAddress
* #param port
* #throws Exception
*/
public TCPServer(String ipAddress, int port) throws Exception {
if (ipAddress != null && !ipAddress.isEmpty())
this.server = new ServerSocket(port, 1, InetAddress.getByName(ipAddress));
else
this.server = new ServerSocket(0, 1, InetAddress.getLocalHost());
}
/**
* The listen method listen to incoming client's datagrams and requests
* #throws Exception
*/
private void listen() throws Exception {
// listen to incoming client's requests via the ServerSocket
//add your code here
String data = null;
Socket client = this.server.accept();
String clientAddress = client.getInetAddress().getHostAddress();
System.out.println("\r\nNew client connection from " + clientAddress);
// print received datagrams from client
//add your code here
BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));
while ( (data = in.readLine()) != null ) {
System.out.println("\r\nMessage from " + clientAddress + ": " + data);
client.sendUrgentData(1);
}
}
public InetAddress getSocketAddress() {
return this.server.getInetAddress();
}
public int getPort() {
return this.server.getLocalPort();
}
public static void main(String[] args) throws Exception {
// set the server address (IP) and port number
//add your code here
String serverIP = "192.168.1.235"; // local IP address
int port = 8088;
if (args.length > 0) {
serverIP = args[0];
port = Integer.parseInt(args[1]);
}
// call the constructor and pass the IP and port
//add your code here
TCPServer server = new TCPServer(serverIP, port);
System.out.println("\r\nRunning Server: " +
"Host=" + server.getSocketAddress().getHostAddress() +
" Port=" + server.getPort());
server.listen();
}
}
What can i add to my existing server code to make it handle email for my Client. I'll also post my email client as well.
ClientTester.java
import java.io.*;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.Socket;
import java.util.Scanner;
/**
* This program demonstrates a TCP client
* #author jl922223
* #version 1.0
* #since 2020-12-12
*/
public class ClientTester{
private Socket tcpSocket;
private InetAddress serverAddress;
private int serverPort;
private Scanner scanner;
/**
* #param serverAddress
* #param serverPort
* #throws Exception
*/
private ClientTester(InetAddress serverAddress, int serverPort) throws Exception {
this.serverAddress = serverAddress;
this.serverPort = serverPort;
//Initiate the connection with the server using Socket.
//For this, creates a stream socket and connects it to the specified port number at the specified IP address.
//add your code here
this.tcpSocket = new Socket(this.serverAddress, this.serverPort);
this.scanner = new Scanner(System.in);
}
/**
* The start method connect to the server and datagrams
* #throws IOException
*/
/* private void start() throws IOException {
String input;
//create a new PrintWriter from an existing OutputStream (i.e., tcpSocket).
//This convenience constructor creates the necessary intermediateOutputStreamWriter, which will convert characters into bytes using the default character encoding
//You may add your code in a loop so that client can keep send datagrams to server
//add your code here
while (true) {
System.out.print ("C:");
input = scanner.nextLine();
PrintWriter output = new PrintWriter(this.tcpSocket.getOutputStream(), true);
output.println(input);
output.flush();
}
}*/
public static void main(String[] args) throws Exception {
// set the server address (IP) and port number
//add your code here
//IP: 192.168.1.235
//Port: 8088
InetAddress serverIP = InetAddress.getByName("smtp.google.com"); // local IP address
int port = 25;
if (args.length > 0) {
serverIP = InetAddress.getByName(args[0]);
port = Integer.parseInt(args[1]);
}
// call the constructor and pass the IP and port
//add your code here
ClientTester client = new ClientTester(serverIP, port);
// client.start();
try{
client = new ClientTester(serverIP, port);
System.out.println("\r\n Connected to Server: " + client.tcpSocket.getInetAddress());
BufferedReader stdin;
stdin = new BufferedReader (new InputStreamReader (System.in));
InputStream is = client.tcpSocket.getInputStream ();
BufferedReader sockin;
sockin = new BufferedReader (new InputStreamReader (is));
OutputStream os = client.tcpSocket.getOutputStream();
PrintWriter sockout;
sockout = new PrintWriter (os, true);
System.out.println ("S:" + sockin.readLine ());
while (true){
System.out.print ("C:");
String cmd = stdin.readLine ();
sockout.println (cmd);
String reply = sockin.readLine ();
System.out.println ("S:" + reply);
if (cmd.toLowerCase ().startsWith ("data") &&
reply.substring (0, 3).equals ("354"))
{
do
{
cmd = stdin.readLine ();
if (cmd != null && cmd.length () > 1 &&
cmd.charAt (0) == '.')
cmd = "."; // Must be no chars after . char.
sockout.println (cmd);
if (cmd.equals ("."))
break;
}
while (true);
// Read a reply string from the SMTP server program.
reply = sockin.readLine ();
// Display the first line of this reply string.
System.out.println ("S:" + reply);
continue;
}
// If the QUIT command was entered, quit.
if (cmd.toLowerCase ().startsWith ("quit"))
break;
}
}
catch (IOException e)
{
System.out.println (e.toString ());
}
finally
{
try
{
// Attempt to close the client socket.
if (client != null)
client.tcpSocket.close();
}
catch (IOException e)
{
}
}
}
}
The good news is that the ClientTester works when i connect it to smtp.google.com but i don't want to use Googles, i want to have my own basic Email server in java.
Okay, found this early-development standalone version.
Use this INSTEAD of your code; does everything your code does an more.
Single-threaded ServerSocket handling, so only one connection at a time.
package jc.lib.io.net.email.smtp.test1;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.text.SimpleDateFormat;
import java.util.Date;
import jc.lib.io.net.email.JcEMailBasics;
public class Test_SMTP_Server {
static public boolean DEBUG = true;
public static void main(final String s[]) throws UnknownHostException, IOException {
final Test_SMTP_Server server = new Test_SMTP_Server(JcEMailBasics.SMTP_PORTS);
server.start();
try {
Thread.sleep(1 * 60 * 60 * 1000);
} catch (final InterruptedException e) { /* */ }
}
/*
* OBJECT
*/
private final ServerSocket[] mSockets;
private volatile boolean mStopRequested;
private static boolean mReceivingData;
public Test_SMTP_Server(final int[] pPorts) throws IOException {
mSockets = new ServerSocket[pPorts.length];
for (int i = 0; i < pPorts.length; i++) {
final int port = pPorts[i];
try {
mSockets[i] = new ServerSocket(port);
} catch (final java.net.BindException e) {
new java.net.BindException("When mountin port " + port + ": " + e.getMessage()).printStackTrace();
}
System.out.println("Created server socket on port " + port);
}
}
public void start() {
mStopRequested = false;
for (final ServerSocket ss : mSockets) {
if (ss == null) continue;
final Thread t = new Thread(() -> handleServerSocket(ss), "handleServerSocket(" + ss.getLocalPort() + ")");
t.setDaemon(true);
t.start();
}
}
private void handleServerSocket(final ServerSocket pSS) {
final String name = "handleServerSocket(" + pSS.getLocalPort() + ")";
while (!mStopRequested) {
System.out.println(name + "\tListening for connection...");
try (final Socket socket = pSS.accept();
final BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream(), JcEMailBasics.DEFAULT_CHARSET_SMTP_POP3));
final BufferedWriter out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), JcEMailBasics.DEFAULT_CHARSET_SMTP_POP3));) {
System.out.println(name + "\tGot new Socket.");
handle(socket, in, out);
System.out.println(name + "\tClosing Socket.");
} catch (final IOException e) {
System.err.println("In " + name + ":");
e.printStackTrace();
}
System.out.println(name + "\tComm Done.");
}
}
public void stop() {
mStopRequested = true;
for (final ServerSocket ss : mSockets) {
try {
ss.close();
} catch (final Exception e) { /* */ }
}
}
static private void handle(final Socket pSocket, final BufferedReader pBR, final BufferedWriter pBW) throws IOException {
// send("+OK POP3 server ready <" + Test_EMails.SERVICE_ADDRESS + ">", out);
send("220 cbsoft.dev SMTP " + JcEMailBasics.NAME, pBW);
final StringBuilder sb = new StringBuilder();
mainLoop: while (!pSocket.isClosed()) {
final String read = read(pBR);
if (read == null) break;
switch (read) {
case JcEMailBasics.COMMAND_DATA: {
send("354 End data with <CR><LF>.<CR><LF>", pBW);
mReceivingData = true;
break;
}
case JcEMailBasics.COMMAND_END_OF_DATA: {
send("250 OK", pBW);
mReceivingData = false;
break;
}
case JcEMailBasics.COMMAND_QUIT: {
send("221 " + JcEMailBasics.NAME + " signing off", pBW);
break mainLoop;
}
default: {
final String correctedRead = read.startsWith(".") ? read.substring(1) : read;
sb.append(correctedRead + "\n");
if (!mReceivingData) send("250 Ok", pBW);
}
}
}
final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");
final File file = new File("mails/inc_" + sdf.format(new Date()) + ".email.txt");
file.getParentFile().mkdirs();
final String msg = sb.toString();
try (FileOutputStream fos = new FileOutputStream(file)) {
fos.write(msg.getBytes());
}
System.out.println("File saved as " + file.getCanonicalPath());
}
static private void send(final String pMessage, final BufferedWriter pBW) {
try {
pBW.write(pMessage + "\n");
pBW.flush();
if (DEBUG) System.out.println("SENT:\t" + pMessage);
} catch (final Exception e) {
e.printStackTrace();
}
}
static private String read(final BufferedReader pBR) throws IOException {
try {
final String reply = pBR.readLine();
if (DEBUG) System.out.println("RECV:\t" + reply);
return reply;
} catch (final SocketTimeoutException e) {
System.err.println("SERVER TIMEOUT");
}
return null;
}
}
the only additional file you will need (also included in my previous answer; edited a bit):
package jc.lib.io.net.email;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
public class JcEMailBasics {
static public final int SMTP_PORT_1 = 25;
static public final int SMTP_PORT_2 = 587;
static public final int SMTP_PORT_3 = 465;
static public final int[] SMTP_PORTS = { SMTP_PORT_1, SMTP_PORT_2, SMTP_PORT_3 };
static public final int POP_PORT_1 = 110;
static public final int POP_PORT_SSL = 995;
static public final int POP_PORT_KERBEROS = 1109;
static public final int[] POP_PORTS = { POP_PORT_1, POP_PORT_SSL, POP_PORT_KERBEROS };
// netstat -aon | findstr '587'
static public final String DEFAULT_CHARSET_SMTP_POP3 = "8859_1";
static public final String NAME = "JC Oblivionat0r POP3 Server";
static public final String SERVICE_ADDRESS = "oblivionat0r#cbsoft.dev";
static public final String CONNECTION_CLOSED = "CONNECTION_CLOSED_dtnt495n3479r5zb3tr47c3b49c3";
static public final String COMMAND_QUIT = "QUIT";
static public final String COMMAND_DATA = "DATA";
static public final String COMMAND_END_OF_DATA = ".";
static public void send(final BufferedWriter pBufferedWriter, final String pMessage) throws IOException {
pBufferedWriter.write(pMessage + "\n");
pBufferedWriter.flush();
System.out.println("SENT:\t" + pMessage);
}
static public String sendExpect(final BufferedWriter pBufferedWriter, final String pMessage, final BufferedReader pBufferedReader, final String... pExpectedResponsePrefixes) throws IOException {
send(pBufferedWriter, pMessage);
final String read = read(pBufferedReader);
for (final String erp : pExpectedResponsePrefixes) {
if (read.startsWith(erp)) return read;
}
throw new IllegalStateException("Bad response: Expected [" + toString(", ", pExpectedResponsePrefixes) + "] got [" + read + "] instead!");
}
static public String read(final BufferedReader pBufferedReader) throws IOException {
final String reply = pBufferedReader.readLine();
System.out.println("RECV:\t" + reply);
return reply;
}
#SafeVarargs public static <T> String toString(final String pSeparator, final T... pObjects) {
if (pObjects == null) return null;
final StringBuilder ret = new StringBuilder();
for (final T o : pObjects) {
ret.append(o + pSeparator);
}
if (ret.length() > 0) ret.setLength(ret.length() - pSeparator.length());
return ret.toString();
}
}
Basically like my code.
It is just a proof of concept, and quite unsafe and inefficient
I'm using lombok. The read() method is basically a BufferedReader.readLine() call on the socket's InputStream.
send() is a writeLine
My entry point handleSocket() is when the Socket connection is established.
The String.toNLine() method is a Lombok extension, you can replace it with string.replace("\r\n" , "\n");
Be aware that this is simply a stupid implementation that can be fooled easily, but it enables basic email receiving. You get ALL the communication in the StringBuilder. You could take that final whole text apart with MIME classes (Header / newline / newline body method that is used by HTTP, SMTP etc).
This approach collects the whole comunication first, then later (outside given code) handles the actual MIME part. You could also implement it differently, as in the code knows the current state of transmission and details of the MIME object it's currently receiving, and updates its status/workflow with each line. That would be much more efficient, but the code would be a bit more complex.
package jc.lib.io.net.email.smtp.server.receiver;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Date;
import jc.lib.aop.lombok.java.lang.JcAString;
import jc.lib.collection.tuples.JcTriple;
import jc.lib.io.net.email.JcEMailBasics;
import jc.lib.io.net.email.util.JcAServerSocketHandlerBase;
import jc.lib.lang.thread.event.JcEvent;
import lombok.experimental.ExtensionMethod;
#ExtensionMethod({ JcAString.class })
public class JcSmtpReceiverSocketHandler extends JcAServerSocketHandlerBase {
public final JcEvent<JcTriple<JcSmtpReceiver, JcSmtpReceiverSocketHandler, File>> EVENT_EMAIL_RECEIVED = new JcEvent<>();
private final JcSmtpReceiver mJcAServerBase;
private boolean mReceivingData;
public JcSmtpReceiverSocketHandler(final JcSmtpReceiver pJcAServerBase, final ServerSocket pServerSocket, final Socket pSocket) throws IOException {
super(pServerSocket, pSocket);
mJcAServerBase = pJcAServerBase;
}
#Override protected void handleSocket() throws IOException {
send("220 cbsoft.dev SMTP " + JcEMailBasics.NAME);
final StringBuilder sb = new StringBuilder();
mainLoop: while (!mSocket.isClosed()) {
final String read = read();
if (read == null) break;
switch (read) {
case JcEMailBasics.COMMAND_DATA: {
send("354 End data with <CR><LF>.<CR><LF>");
mReceivingData = true;
break;
}
case JcEMailBasics.COMMAND_END_OF_DATA: {
send("250 OK");
mReceivingData = false;
break;
}
case JcEMailBasics.COMMAND_QUIT: {
send("221 " + JcEMailBasics.NAME + " signing off");
break mainLoop;
}
default: {
final String correctedRead = read.startsWith(".") ? read.substring(1) : read;
sb.append(correctedRead + "\n");
if (!mReceivingData) send("250 Ok");
}
}
}
final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");
final File file = new File("mails/inc_" + sdf.format(new Date()) + ".email.txt");
file.getParentFile().mkdirs();
String msg = sb.toString();
msg = msg.toNLineBreak();
final String header = msg.subStringBefore("\n\n");
System.out.println("header:");
try (FileOutputStream fos = new FileOutputStream(file)) {
fos.write(msg.getBytes());
}
System.out.println("File saved as " + file.getCanonicalPath());
EVENT_EMAIL_RECEIVED.trigger(new JcTriple<>(mJcAServerBase, this, file));
}
}
Check out this file for some ports and other info.
package jc.lib.io.net.email;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import jc.lib.io.net.email.util.JcAServerBase;
import jc.lib.lang.JcUArray;
public class JcEMailBasics {
static public final int SMTP_PORT_1 = 25;
static public final int SMTP_PORT_2 = 587;
static public final int SMTP_PORT_3 = 465;
static public final int[] SMTP_PORTS = { SMTP_PORT_1, SMTP_PORT_2, SMTP_PORT_3 };
static public final int POP_PORT_1 = 110;
static public final int POP_PORT_SSL = 995;
static public final int POP_PORT_KERBEROS = 1109;
static public final int[] POP_PORTS = { POP_PORT_1, POP_PORT_SSL, POP_PORT_KERBEROS };
// netstat -aon | findstr '587'
static public final String DEFAULT_CHARSET_SMTP_POP3 = "8859_1";
static public final String NAME = "JC Oblivionat0r POP3 Server";
static public final String SERVICE_ADDRESS = "oblivionat0r#cbsoft.dev";
static public final String CONNECTION_CLOSED = "CONNECTION_CLOSED_dtnt495n3479r5zb3tr47c3b49c3";
static public final String COMMAND_QUIT = "QUIT";
static public final String COMMAND_DATA = "DATA";
static public final String COMMAND_END_OF_DATA = ".";
static public void send(final BufferedWriter pBufferedWriter, final String pMessage) throws IOException {
pBufferedWriter.write(pMessage + "\n");
pBufferedWriter.flush();
if (JcAServerBase.DEBUG) System.out.println("SENT:\t" + pMessage);
}
static public String sendExpect(final BufferedWriter pBufferedWriter, final String pMessage, final BufferedReader pBufferedReader, final String... pExpectedResponsePrefixes) throws IOException {
send(pBufferedWriter, pMessage);
final String read = read(pBufferedReader);
for (final String erp : pExpectedResponsePrefixes) {
if (read.startsWith(erp)) return read;
}
throw new IllegalStateException("Bad response: Expected [" + JcUArray.toString(", ", pExpectedResponsePrefixes) + "] got [" + read + "] instead!");
}
static public String read(final BufferedReader pBufferedReader) throws IOException {
final String reply = pBufferedReader.readLine();
if (JcAServerBase.DEBUG) System.out.println("RECV:\t" + reply);
return reply;
}
}
You need to communicate with the client.
First let the server send something like "220 Smtp server" (only 220 matters) to the client.
I used PrintWriter:
PrintWriter out = new PrintWriter(client.getOutputStream(), true);
out.println("220 Smtp server");
Then you will receive an EHLO from the client while getting lines from the inputStream.
Here you can find an example of the communication between server an client (without the starting message from the server (220)):
https://postmarkapp.com/guides/everything-you-need-to-know-about-smtp#basic-smtp-commands
I am currently having an issue implementing the jsch library to grab information from a ZyXel switch. The program itself will grab some information to confirm the type of switch and then upload the correct firmware and config.
My issue, to me, appears to be a buffer issue. I have no problems sending the command but when I send it, depending on when i run it or how often, I either get half the information I should be getting or all of it. I think it is because sometimes the buffer doesn't empty all the way into the ByteArrayInputStream but at this point I am at a lost. I was wondering if anyone can point me into the right direction on what i am getting wrong. I assume it is a basic InputStream or jsch documentation issue misunderstanding
Thanks!..my code is below.
package ssh;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import com.jcraft.jsch.*;
public class ssh {
private static String user = "admin";
private static String host = "192.168.1.1";
private static String password = "1234";
private static String command = "";
public static void startAutomation() {
JSch jsch = new JSch();
Session session = null;
OutputStream output = null;
Channel channel = null;
try {
session = jsch.getSession(user,host,22);
session.setPassword(password);
session.setConfig("StrictHostKeyChecking","no");
session.connect();
channel = session.openChannel("shell");
command = "show system-information\n";
output = runCommand(command, session, channel);
String test = "NOTHING";
if (output.toString().contains("ES-2024A")) {
test = "true";
command = "show run\n";
output = runCommand(command,session,channel);
} else {
test = "false";
}
System.out.println(test + " This is a 2024A");
} catch (JSchException | InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
channel.disconnect();
session.disconnect();
}
}
public static OutputStream runCommand(String c,Session session,Channel channel) throws InterruptedException, JSchException{
InputStream is = new ByteArrayInputStream(c.getBytes());
channel.setInputStream(is);
OutputStream outputInfo = new ByteArrayOutputStream();
channel.setOutputStream(outputInfo);
channel.connect(15*1000);
try {
is.close();
outputInfo.flush();
outputInfo.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return outputInfo;
}
}
I'm work on similar project, now I created global output stream which doesn't need to wait anything.
I re-written OutputStream to use JTextArea as output
private java.util.List<Character> buffer = new ArrayList<Character>();
public class AreaOutputStream extends OutputStream {
private JTextArea textArea;
public AreaOutputStream(JTextArea textArea) {
this.textArea = textArea;
}
#Override
public synchronized void write(int b) throws IOException {
// collect all character into the buffer
buffer.add((char)
// on '\n' character append to output window
if (String.valueOf((char) b).equals("\n")) {
StringBuilder sb = new StringBuilder(buffer.size());
for (Character c : buffer) sb.append(c);
String line = sb.toString();
// filter output from special characters
//and replace invitation '[username#server~]$ ' with current time
line = line.replaceAll(" \r", "");
line = line.replaceAll("\r", "");
line = line.replaceAll(String.valueOf(Pattern.compile("\\[[0-9][^m ]*m")), "");
line = line.replaceAll(String.valueOf(Pattern.compile("\\[.*#[^$]*\\$ ")), DateFormat.getTimeInstance().format(java.lang.System.currentTimeMillis()) + " ");
line = line.replaceAll(String.valueOf(Pattern.compile("\u001B")), "");
if (!line.matches("(.*)Last login(.*)from(.*)\n")) {
textArea.append(line);
textArea.setCaretPosition(textArea.getDocument().getLength());
}
buffer.clear();
}
}
}
So, to use this output stream in section where you create your channel enough set following:
channel.setOutputStream(new AreaOutputStream(TEXT_AREA_NAME))
So, you can create another channels to execute commands and set as output this class - all these will appear in JTextArea.
BTW now I try to create InputStream to send commands in one channel (for contact please replace 'domain.name' with 'mail.ru')
Hope this helps and sorry for my english.
In my quest to create a simple Java program to extract tweets from Twitter's streaming API, I have modified this (http://cotdp.com/dl/TwitterConsumer.java) code snippet to work with the OAuth method. The result is the below code, which when executed, throws a Connection Refused Exception.
I am aware of Twitter4J however I want to create a program that relies least on other APIs.
I have done my research and it looks like the oauth.signpost library is suitable for Twitter's streaming API. I have also ensured my authentication details are correct. My Twitter Access level is 'Read-only'.
I couldn't find a simple Java example that shows how to use the streaming API without relying on e.g. Twitter4j.
import java.io.BufferedReader;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import org.apache.http.HttpResponse;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import oauth.signpost.OAuthConsumer;
import oauth.signpost.commonshttp.CommonsHttpOAuthConsumer;
/**
* A hacky little class illustrating how to receive and store Twitter streams
* for later analysis, requires Apache Commons HTTP Client 4+. Stores the data
* in 64MB long JSON files.
*
* Usage:
*
* TwitterConsumer t = new TwitterConsumer("username", "password",
* "http://stream.twitter.com/1/statuses/sample.json", "sample");
* t.start();
*/
public class TwitterConsumer extends Thread {
//
static String STORAGE_DIR = "/tmp";
static long BYTES_PER_FILE = 64 * 1024 * 1024;
//
public long Messages = 0;
public long Bytes = 0;
public long Timestamp = 0;
private String accessToken = "";
private String accessSecret = "";
private String consumerKey = "";
private String consumerSecret = "";
private String feedUrl;
private String filePrefix;
boolean isRunning = true;
File file = null;
FileWriter fw = null;
long bytesWritten = 0;
public static void main(String[] args) {
TwitterConsumer t = new TwitterConsumer(
"XXX",
"XXX",
"XXX",
"XXX",
"http://stream.twitter.com/1/statuses/sample.json", "sample");
t.start();
}
public TwitterConsumer(String accessToken, String accessSecret, String consumerKey, String consumerSecret, String url, String prefix) {
this.accessToken = accessToken;
this.accessSecret = accessSecret;
this.consumerKey = consumerKey;
this.consumerSecret = consumerSecret;
feedUrl = url;
filePrefix = prefix;
Timestamp = System.currentTimeMillis();
}
/**
* #throws IOException
*/
private void rotateFile() throws IOException {
// Handle the existing file
if (fw != null)
fw.close();
// Create the next file
file = new File(STORAGE_DIR, filePrefix + "-"
+ System.currentTimeMillis() + ".json");
bytesWritten = 0;
fw = new FileWriter(file);
System.out.println("Writing to " + file.getAbsolutePath());
}
/**
* #see java.lang.Thread#run()
*/
public void run() {
// Open the initial file
try { rotateFile(); } catch (IOException e) { e.printStackTrace(); return; }
// Run loop
while (isRunning) {
try {
OAuthConsumer consumer = new CommonsHttpOAuthConsumer(consumerKey, consumerSecret);
consumer.setTokenWithSecret(accessToken, accessSecret);
HttpGet request = new HttpGet(feedUrl);
consumer.sign(request);
DefaultHttpClient client = new DefaultHttpClient();
HttpResponse response = client.execute(request);
BufferedReader reader = new BufferedReader(
new InputStreamReader(response.getEntity().getContent()));
while (true) {
String line = reader.readLine();
if (line == null)
break;
if (line.length() > 0) {
if (bytesWritten + line.length() + 1 > BYTES_PER_FILE)
rotateFile();
fw.write(line + "\n");
bytesWritten += line.length() + 1;
Messages++;
Bytes += line.length() + 1;
}
}
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("Sleeping before reconnect...");
try { Thread.sleep(15000); } catch (Exception e) { }
}
}
}
}
I tried to simulate the code and found that the error was very simple. You should use https instead of http in the url :)
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 6 years ago.
Improve this question
Reinstating this question with a bounty! I need an example that stays online, like a real instant messenger! It needs to always be ready to receive or send a message to an arbitrary address over an arbitrary port, using TCP. The program must not quit after sending/receiving a message.
Bounty goes to whoever can give the best example of a real, usable instant messenger.
Looking online, all resources I found are either useless tutorials, dead threads, dead tutorials, ancient examples, or tell the programmer to use external APIs. How can I create a basic instant messenger from the ground up, only using Java SE?
There must be a way to do this, and some sample code would be appreciated. It only needs to perform the simplest tasks: Check if a compatible client is online on another computer (IP will be provided by the user) and send a TCP packet to that client, which will receive and display its contents.
When this question was first asked and answered back in 2011, it was simply "Looking online, all resources I found are either useless
tutorials, dead threads, or tell the programmer to use external
APIs.". The provided links below met the criteria at the time. Further discussion follows in the comments.
First few Google results for "java socket chat":
http://cs.lmu.edu/~ray/notes/javanetexamples/
simple chatting program in java usings socket class
http://pirate.shu.edu/~wachsmut/Teaching/CSAS2214/Virtual/Lectures/chat-client-server.html
http://www.cise.ufl.edu/~amyles/tutorials/tcpchat/
http://ashishmyles.com/tutorials/tcpchat/index.html
Internet Archive link to fix missing Java source file downloads: https://web.archive.org/web/20150623102646/http://ashishmyles.com/tutorials/tcpchat/index.html
Or from "java 8 chat client":
https://gist.github.com/alex-zykov/b4052e3c1b6891081897
Many, many results following in the search. Pick one that suits your needs. You can even modify the Google search to only show results from the past year, if you wish.
I've done this when I was learning Java, something around 10 years ago. It works:
Constantes.java:
package jsc;
public interface Constantes {
public static final String MULTICAST_IP = "224.0.0.1";
public static final int MULTICAST_PORTA = 3333;
public static final String SEPARADOR = "[>>>]";
public static final int TAMANHO_MENSAGEM = 1024;
public static final long ESPERA = 3000;
public static final String ESTOUONLINE = "EstouOnline";
public static final String DESCONECTANDO = "Desconectando";
public static final String PRIVADO = "Privado";
}
ControladorThread.java
package jsc;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.net.UnknownHostException;
import java.util.Date;
import java.util.Iterator;
import java.util.StringTokenizer;
import java.util.Vector;
public class ControladorThread extends Thread implements Constantes{
private MulticastSocket mcSocket;
private Main main;
private Vector<Usuario> listaUsuarios; // lista de usuários ativos
public ControladorThread(Main main){
super("ReceptoraThread_" + main.getNick());
listaUsuarios = new Vector<Usuario>();
listaUsuarios.add(new Usuario(main.getNick(), new Date().getTime()));
this.main = main;
try{
mcSocket = new MulticastSocket(MULTICAST_PORTA);
mcSocket.joinGroup(InetAddress.getByName(MULTICAST_IP));
} catch(IOException e){
e.printStackTrace();
}
}
public void run(){
while(true){
try{
byte[] buffer = receberPacote();
processar(buffer);
removerUsuariosOciosos();
atualizarListaUsuarios();
} catch(IOException e){
e.printStackTrace();
}
}
}
public byte [] receberPacote() throws IOException{
byte[] buffer = new byte[TAMANHO_MENSAGEM];
DatagramPacket pacote = new DatagramPacket(buffer, buffer.length);
mcSocket.receive(pacote);
return buffer;
}
public void processar(byte[] buffer){
String mensagem = new String(buffer);
mensagem = mensagem.trim();
StringTokenizer tokens = new StringTokenizer(mensagem, SEPARADOR);
String t1 = tokens.nextToken();
String t2 = tokens.nextToken();
if(t1.equals(ESTOUONLINE))
atualizarEstadoUsuario(t2);
else if(t1.equals(DESCONECTANDO))
desconectarUsuario(t2);
else if(t1.equals(PRIVADO)){
String t3 = tokens.nextToken();
String t4 = tokens.nextToken();
if(t3.equals(main.getNick())){
receberMensagemPrivada(t2, t4);
}
}
else
main.setTextoEntrada(t1 + " diz: " + t2);
}
public void receberMensagemPrivada(String deUsuario, String mensagem){
main.abrirChatPrivado(main.getNick(), deUsuario, mensagem);
}
public boolean atualizarEstadoUsuario(String nomeUsuario){
int pos;
for(Iterator i = listaUsuarios.iterator(); i.hasNext(); ){
Usuario uAux = (Usuario) i.next();
if(uAux.getNome().equals(nomeUsuario)){
pos = listaUsuarios.indexOf(uAux);
listaUsuarios.remove(uAux);
uAux.setTempoInicio(new Date().getTime());
listaUsuarios.add(pos, uAux);
return true;
}
}
listaUsuarios.add(new Usuario(nomeUsuario, new Date().getTime()));
return false;
}
public void removerUsuariosOciosos(){
Usuario usuario = null;
for(Iterator i = listaUsuarios.iterator(); i.hasNext(); ){
usuario = (Usuario) i.next();
if(new Date().getTime() - usuario.getTempoInicio() > ESPERA){
desconectarUsuario(usuario.getNome());
i = listaUsuarios.iterator();
}
}
}
public void desconectarUsuario(String nomeUsuario){
for(Iterator i = listaUsuarios.iterator(); i.hasNext(); ){
Usuario uAux = (Usuario) i.next();
if(uAux.getNome().equals(nomeUsuario)){
i.remove();
break;
}
}
}
public void atualizarListaUsuarios(){
Vector<String> sVector = new Vector<String>();
Usuario uAux = null;
System.out.println("\nOnline: ");
for(Iterator i = listaUsuarios.iterator(); i.hasNext(); ){
uAux = (Usuario) i.next();
System.out.print( uAux.getNome() + " ");
sVector.add(uAux.getNome());
}
main.setUsuariosOnline(sVector);
}
private class Usuario{
private String nome;
private long tempoInicio;
public Usuario(){}
public Usuario(String nome, long tempoInicio){
this.nome = nome;
this.tempoInicio = tempoInicio;
}
public String getNome() {
return nome;
}
public void setNome(String nome) {
this.nome = nome;
}
public long getTempoInicio() {
return tempoInicio;
}
public void setTempoInicio(long tempoInicio) {
this.tempoInicio = tempoInicio;
}
}
public void sair(){
try {
mcSocket.leaveGroup(InetAddress.getByName(MULTICAST_IP));
mcSocket.close();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
EstouOnlineThread.java
package jsc;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
public class EstouOnlineThread extends Thread implements Constantes{
private MulticastSocket mcSocket;
private String nick;
private byte[] buffer;
public EstouOnlineThread(String nick){
super("EstouOnlineThread_" + nick);
this.nick = nick;
try {
mcSocket = new MulticastSocket();
} catch(IOException e) {
e.printStackTrace();
}
}
public void run(){
String saida = ESTOUONLINE + SEPARADOR + nick;
buffer = saida.getBytes();
while(true){
try{
DatagramPacket estouOnline = new DatagramPacket(buffer, buffer.length, InetAddress.getByName(MULTICAST_IP), MULTICAST_PORTA);
mcSocket.send(estouOnline);
System.out.println(saida);
sleep(ESPERA);
}
catch(InterruptedException e){
e.printStackTrace();
}
catch(IOException e){
e.printStackTrace();
}
}
}
}
MensagemPrivadaFrame.java
package jsc;
import java.awt.BorderLayout;
import java.awt.Frame;
import java.awt.TextArea;
import java.awt.TextField;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.net.UnknownHostException;
public class MensagemPrivadaFrame extends Frame implements Constantes{
private static final long serialVersionUID = 1L;
private TextArea entrada;
private TextField saida;
private String nomeJanela;
private String nick;
private String paraUsuario;
private MulticastSocket mcSocket;
private ActionListener saidaListener;
private WindowAdapter frameListener;
private boolean estouVivo; // indica que a janela ainda está ativa
public MensagemPrivadaFrame(String nick, String paraUsuario){
super("JSC - Chat com " + paraUsuario);
setIconImage(Toolkit.getDefaultToolkit().getImage("icone.4"));
this.nick = nick;
this.paraUsuario = paraUsuario;
this.nomeJanela = nick + paraUsuario;
try {
mcSocket = new MulticastSocket();
} catch (IOException e) {
e.printStackTrace();
}
iniciarComponentes();
estouVivo = true;
}
public void setNomeJanela(String nomeJanela){
this.nomeJanela = nomeJanela;
}
public String getNomeJanela(){
return nomeJanela;
}
public String getNick() {
return nick;
}
public void setNick(String nick) {
this.nick = nick;
}
public boolean estouVivo(){
return estouVivo;
}
public void iniciarComponentes(){
saidaListener = new ActionListener(){
public void actionPerformed(ActionEvent e){
TextField origem = (TextField) e.getSource();
enviarMensagem(origem.getText());
entrada.append("\n(" + nick + " diz) " + origem.getText());
origem.setText("");
}
};
frameListener = new WindowAdapter(){
public void windowClosing(WindowEvent e){
estouVivo = false;
dispose();
}
};
entrada = new TextArea("[JSC] Bate papo privado entre " + nick + " e " + paraUsuario + "\n");
entrada.setEditable(false);
saida = new TextField();
saida.addActionListener(saidaListener);
addWindowListener(frameListener);
setLayout(new BorderLayout());
int x = (int) (Math.random() * 500);
int y = (int) (Math.random() * 500);
setBounds(x, y, 400, 300);
System.out.println(x + " " + y);
add("Center", entrada);
add("South", saida);
setVisible(true);
saida.requestFocus();
}
public void setTextoEntrada(String texto){
entrada.append("\n" + texto);
entrada.setCaretPosition(entrada.getText().length());
}
public void enviarMensagem(String mensagem){
try{
mensagem = PRIVADO + SEPARADOR + nick + SEPARADOR + paraUsuario + SEPARADOR + mensagem;
byte[] bMensagem = mensagem.getBytes();
DatagramPacket pacote = new DatagramPacket(bMensagem, bMensagem.length, InetAddress.getByName(MULTICAST_IP), MULTICAST_PORTA);
mcSocket.send(pacote);
}
catch(UnknownHostException e){
e.printStackTrace();
}
catch(IOException e){
e.printStackTrace();
}
}
}
Main.java
package jsc;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.ScrollPane;
import java.awt.TextArea;
import java.awt.TextField;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.net.UnknownHostException;
import java.util.Iterator;
import java.util.Vector;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
public class Main extends Frame implements Constantes{
private static final long serialVersionUID = 1L;
private TextArea entrada;
private TextField saida;
private JList usuariosOnline;
private ScrollPane usuariosOnlineScroll;
private WindowAdapter mainListener;
private ActionListener saidaListener;
private MouseAdapter listListener;
private MulticastSocket mcSocket; // soquete para multicasting
private Vector<String> listaUsuariosOnline; // lista com os nomes de usuários online
private Vector<MensagemPrivadaFrame> listaJanelasAbertas; // janelas de conversação privadas abertas
private String nick; // nome do usuário no chat
public void setNick(String nick){
this.nick = nick;
}
public String getNick(){
return nick;
}
public Main(String nick){
super("Java Socket Chat [" + nick + "]");
setIconImage(Toolkit.getDefaultToolkit().getImage("icone.1"));
this.nick = nick;
listaUsuariosOnline = new Vector<String>();
listaUsuariosOnline.add(nick);
listaJanelasAbertas = new Vector<MensagemPrivadaFrame>();
try{
mcSocket = new MulticastSocket();
}
catch(IOException e){
e.printStackTrace();
}
iniciarComponentes();
new EstouOnlineThread(nick).start();
new ControladorThread(this).start();
}
public void iniciarComponentes(){
mainListener = new WindowAdapter(){
public void windowClosing(WindowEvent e){
sair();
}
};
saidaListener = new ActionListener(){
public void actionPerformed(ActionEvent e){
TextField origem = (TextField) e.getSource();
enviarMensagem(origem.getText());
origem.setText("");
}
};
listListener = new MouseAdapter(){
public void mouseClicked(MouseEvent e){
if( e.getClickCount() >= 2 ){
// abrir a janela para mensagens privadas e passar o id do usuário
JList jlAux = (JList) e.getSource();
String paraUsuario = (String) jlAux.getSelectedValue();
abrirChatPrivado(nick, paraUsuario, null);
}
}
};
usuariosOnline = new JList(listaUsuariosOnline);
usuariosOnline.setSize(new Dimension(60, 280));
usuariosOnlineScroll = new ScrollPane();
usuariosOnlineScroll.add(usuariosOnline);
entrada = new TextArea("Olá " + nick);
entrada.setEditable(false);
entrada.setSize(300,280);
saida = new TextField();
saida.addActionListener(saidaListener);
usuariosOnline.addMouseListener(listListener);
usuariosOnline.setMinimumSize(new Dimension(60, 250));
addWindowListener(mainListener);
setSize(400, 300);
setLayout(new BorderLayout());
add("North", new JLabel("Java Socket ChatO"));
add("Center", entrada);
add("South", saida);
add("East", usuariosOnlineScroll);
setVisible(true);
requestFocus();
}
public void enviarMensagem(String mensagem){
try{
mensagem = nick + SEPARADOR + mensagem;
byte[] bMensagem = mensagem.getBytes();
DatagramPacket pacote = new DatagramPacket(bMensagem, bMensagem.length, InetAddress.getByName(MULTICAST_IP), MULTICAST_PORTA);
mcSocket.send(pacote);
}
catch(UnknownHostException e){
e.printStackTrace();
}
catch(IOException e){
e.printStackTrace();
}
}
private void desconectando(){
try{
String mensagem = "Desconectando" + SEPARADOR + nick;
byte[] bMensagem = mensagem.getBytes();
DatagramPacket pacote = new DatagramPacket(bMensagem, bMensagem.length, InetAddress.getByName(MULTICAST_IP), MULTICAST_PORTA);
mcSocket.send(pacote);
}
catch(UnknownHostException e){
e.printStackTrace();
}
catch(IOException e){
e.printStackTrace();
}
}
public void abrirChatPrivado(String nick, String paraUsuario, String mensagem){
removerJanelasInativas();
if(nick.equals(paraUsuario)){
JOptionPane.showMessageDialog(null, "Você não pode abrir um janela de conversação para você mesmo!", "Burro!", JOptionPane.ERROR_MESSAGE);
return;
}
String nome = nick + paraUsuario;
MensagemPrivadaFrame janela = null;
for(Iterator i = listaJanelasAbertas.iterator(); i.hasNext();){
janela = (MensagemPrivadaFrame) i.next();
if(nome.equals(janela.getNomeJanela())){
System.out.println(nick + " - " + janela.getNomeJanela() + " - " + janela.toString());
janela.setTextoEntrada("(" + paraUsuario + " diz) " + mensagem);
//janela.requestFocus();
return;
}
}
janela = new MensagemPrivadaFrame(nick, paraUsuario);
if(mensagem != null)
janela.setTextoEntrada("(" + paraUsuario + " diz) " + mensagem);
listaJanelasAbertas.add(janela);
//janela.requestFocus();
}
public void removerJanelasInativas(){
MensagemPrivadaFrame janela = null;
for(Iterator i = listaJanelasAbertas.iterator(); i.hasNext(); ){
janela = (MensagemPrivadaFrame) i.next();
if( !janela.estouVivo()){
i.remove();
}
}
}
public void setTextoEntrada(String texto){
entrada.append("\n" + texto);
entrada.setCaretPosition(entrada.getText().length());
}
public void setUsuariosOnline(Vector<String> listaUsuariosOnline){
usuariosOnline.setListData(listaUsuariosOnline);
}
public void sair(){
desconectando();
dispose();
System.exit(0);
}
public static void main(String args[]){
String nick = JOptionPane.showInputDialog("Digite seu nome (max. 20 caracteres): ");
if(nick != null && !nick.equals("")){
if(nick.length() > 20)
nick = nick.substring(0, 20);
new Main(nick);
}
else
JOptionPane.showMessageDialog(null, "É necessário informar um nome para entrar no bate-papo");
//System.exit(0);
}
}
Nowadays I'm not proud of the code, but it really works.
Edit:
As some have suggested, I've made some code improvements (refactoring) and post the project on GitHub: https://github.com/jaumzera/javasocketchat
Hm, I was tempted to direct you to a java implementation of a server implementing imap protocol (eg. gavamail). But this, of cources, might also qualify as "old" code and for sure would kill your expectation (for being a non-standard solution). Nevertheless it is a proper reference fulfilling your (terse) specification.
What do we have?
We need a solution that should be in java.
It must implement an basic instant messaging system.
The later is problematic as it covers a really broad range of functionality. But "basic" seem to allow for a minimum solution.
So what is a minimum instant messaging system? Let's try with the following:
a client that is posting and retrieving messages.
a server that is storing posted messages for (later) retrieval
We also would need a policy of how a client would identify a proper server. Most trivial solution for the later aspect is using a "central" server with a well-known address. For more complex cases we would need to have the server and/or client functionality distributed across several instances and devise a scheme or policy for identifying the proper instance(s) for communication.
We leave out more complex semantics like having different users or messages being related to a system of categories or tags.
Now, we are down to having two components:
A server implementing two entry points:
POST_MESSAGE
receive a mesage form a client and store it for later retrieval
This immediatley is asking the question of where to store such messages (in a database or filesystem for persistency or simply within memory for a "messages live as long as the server is up" semantics)
LOOKUP_MESSAGE
select a suitable message from the stored ones (preferrably an unread one) and return to caller.
This could also return a set of messages (but think of restricting such set for cases where a caller has a severe backlog of messages)
It might be necessary to keep track of the messages already having been read, either by marking the messages or by maintaining seen status at the client. This could even be as simple as keeping time or ordinal of last message seen and send this information along with the LOOKUP_MESSAGE request.
A client needs to interact with a user on one hand and the service on the other hand.
It will take gat a new message from the user (likely on explicit request (e.g. send button) and call the POST_MESSAGE service at the related server.
It also will (likely regularly, could also be on explicit request (e.g. user is starting client)) poll the server for new messages. (Alternatively, you could devise a separate notification service that is used by the server to notify the client of new messages. What suits your "needs" is beyond your question.)
That's it.
So any example of a TCP based client/server application will form a perfect starting point for a straight implementation.
I should also mention that you could cut the specification logic within the client and delegate user interaction to a standard browser and implement the client application logic into a (web-)server instance (together or separate from the server part). Nevertheless, you still will have both (client/server) logical functionality according to above minimum specification.
Another aspect you should be aware of:
With some comments you mentioned "host" and "guest" attributions available in current messenger examples. This actually is a logical structure of a tagging system provided with those messengers. The messages are still sent from a client to a server and then being retrieved by other clients. Whether a client can see a message is determined by the client being eligible to the specific tag. E.g posting a message to a contact from yours (user b) just tags the message with the tag "for_user_b" and as such it is only visible to the poster and anybody that is also allowed to read "for_user_b" tag messages (user b in our example). So, please be aware that the logical structure of a messaging system is determined by the access and distribution policy and not by the physical distribution structure!
I am not even sure if this question is still being used or what but I liked the task and I thought:
why not?
Here is my implementation, as simple as it gets but without forgetting fundamental parts.
Written in pure Java, makes use of, among the rest, Sockets, Threads and SynchronizedList:
SimpleChat.java (Main)
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class SimpleChat {
private static boolean isRunning = true;
private static Sender sender;
private static Receiver receiver;
public static void main(String[] args) throws IOException {
if(args.length < 3){
showUsage();
}
try {
receiver = new Receiver(Integer.parseInt(args[1]));
sender = new Sender(args[0], args[2], Integer.parseInt(args[3]));
} catch (InterruptedException e) {
showUsage();
}
// Read user input
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
System.out.println("Chat started. Type '\\exit' to quit.");
while(isRunning) {
String input = br.readLine();
if(input.equals("\\exit")){
receiver.stop();
sender.stop();
isRunning = false;
} else {
sender.sendMessage(input);
}
}
}
static void showUsage(){
System.out.println("Usage: java SimpleChat.java listening_port target_IP target_port");
System.exit(1);
}
}
Receiver.java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
public class Receiver {
private boolean isRunning = true;
public Receiver(int listeningPort) throws IOException {
Runnable receiverT = new Runnable() {
public void run() {
ServerSocket serverSocket;
try {
serverSocket = new ServerSocket(listeningPort);
Socket clientSocket = serverSocket.accept();
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
while(isRunning) {
try {
System.out.println(in.readLine());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
};
new Thread(receiverT).start();
}
public void stop(){
isRunning = false;
}
}
Sender.java
import java.io.IOException;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
public class Sender {
private boolean isRunning = true;
private volatile List<String> msgs;
public Sender(String username, String targetIP, int targetPort) throws InterruptedException, UnknownHostException, IOException {
msgs = Collections.synchronizedList(new ArrayList<String>());
Runnable senderT = new Runnable() {
public void run() {
try {
Socket socket = new Socket(targetIP, targetPort);
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
while(isRunning) {
synchronized(msgs){
Iterator<String> it = msgs.iterator();
while(it.hasNext()){
out.println(username + ": " + it.next());
}
// Clear messages to send
msgs.clear();
}
}
out.close();
socket.close();
} catch (UnknownHostException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
};
new Thread(senderT).start();
}
public void stop(){
isRunning = false;
}
public void sendMessage(String msg){
synchronized(msgs){
msgs.add(msg);
}
}
}
As the 'showUsage()' says, use this program as follows:
java SimpleChat.java username listening_port target_ip target_port
Example:
java SimpleChat.java Supuhstar 1234 127.0.0.1 1234
[To talk to yourself]
I think you should clarify some details regarding what exactly you mean by a "basic instant messaging program" and what your objectives actually are regarding this project.
In a 2011 comment, you mentioned that there should be no "central hub", but in a more recent comment, you say that you would like something more in line with Skype or iMessage, where users do not have to know which peer is the server... It is technically possible (using protocols such as mdns, dlna or ssdp) for the program to transparently search the local network for potential existing server nodes and have it either connect to the server peer if there is one, or establish itself as a local server for other nodes to connect to it. That is for example how Apple iChat's Bonjour protocol used to work. This is however a rather complex solution to implement right, and is definitely not in line with what is done by current mass market messaging programs.
Also establishing direct peer-to-peer communication between users pose several practical issues (particularly because of firewalls and NAT, but there are also concerns of confidentiality and security). Most protocols therefore relay most messages through the central server, and negotiate a direct connection only for the purpose of file transfers and audio/video calls.
For all these reasons, unless you are looking merely for an example of local network communication between two hosts, you most certainly want two distinct programs: a server and a client.
Then, assuming my assumption is correct, there are two other questions that need to be clarified. First, do you have an actual reason to conceive the protocol by yourself, or would it be acceptable to rather implement an existing protocol (such as XMPP, IRC, SIMPLE... there are tons). Even though these protocols might looks highly complex at first, it is almost always possible to implement only a subset these protocol's features/messages. Designing a naive network protocol by yourself isn't that difficult, but there are tons of potential mistakes (mostly inefficiencies, incompleteness and other minor issues) that you will have to go through. Maybe that is indeed what you are specifically aiming for (that is, gaining experience at designing a network protocol from scratch), but unless it is so, you should seriously opt for implementing an existing protocol. Indeed, working with an existing protocol will not only avoid such design mistakes, but better yet, you will gain significant knowledge from how others (generally experienced protocol designers) actually resolved problems they met along the way. Using an existing protocol will also make it much easier and more interesting for you to develop that program, given that you will for example be able to test your client and server programs independently by connecting from/to an official client/server implementation. You will also be able to exploit exiting protocol-decoders in traffic sniffing tools in order to debug messages going through.
The second important question is how realistic you would like the server program to be, and most importantly in regard to persistance. Should the server maintain a persistant list of users and authenticate them? Should the server store a list of allowed contacts for each user? Should the server allow store messages aimed at a peer that is currently offline or that can't be reached at that exact moment? Real messaging server programs generally do such things, and though implementing such mechanisms isn't highly difficult, it is best considered early in the design of a program's architecture. For example, should you decide that these features are indeed desirable, then it might turn out to be much more interesting for you to immediately design your server around a persistant message queue engine, such as ActiveMQ...
I know this is not the examples you were asking for, but I still hope these thoughts may help you.
As said before, there is a lot of things that you need to put a real chat to work.
But i belive that you want something to start. And if you know the address and the port of the other "client" it is easy.
Extreme simple "chat" implementation
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
public class SimpleChat {
protected boolean running = true;
protected int port;
private Thread server;
public static void main(String... arg) {
//create 2 clients in the localhost to test
SimpleChat app1 = new SimpleChat(8989);
SimpleChat app2 = new SimpleChat(8988);
app1.sendMessage("localhost", 8988, "Message from app1 to app2");
app2.sendMessage("localhost", 8989, "Message from app2 to app1");
System.exit(0); // ugly way to kill the threads and exit
}
public SimpleChat(int port) {
this.port = port;
start();
}
public void start() {
server = new Thread(new Server());
server.start();
}
public boolean sendMessage(String host, int port, String message) {
try {
//Connect to a server on given host and port and "send" the message
InetSocketAddress destination
= new InetSocketAddress(host, port);
Socket s = SocketFactory.getDefault().createSocket();
s.connect(destination);
OutputStream out = s.getOutputStream();
out.write(message.getBytes());
out.flush();
out.close();
s.close();
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
public void messageRecived(String message) {
System.out.println("Message recived: " + message);
}
public void stop() {
this.running = false; // only stop after a socked connection
}
class Server implements Runnable {
public void run() {
try {
//Create a server socket to recieve the connection
ServerSocket ss = ServerSocketFactory.getDefault()
.createServerSocket(port);
while (running) {
Socket s = ss.accept();
InputStream in = s.getInputStream();
StringBuilder message = new StringBuilder();
int len;
byte[] buf = new byte[2048];
while ((len = in.read(buf)) > -1) {
if (len > 0) {
message.append(new String(buf, 0, len));
}
}
messageRecived(message.toString());
}
} catch (Exception e) {
e.printStackTrace();
System.exit(-1);
}
}
}
}
I am using Restlet to implement a web service. The client (also uses Restlet) makes many consecutive calls to the server, but after a small number of calls complete successfully, further calls hang the server, which shows the message:
INFO: Stop accepting new connections and transactions. Consider increasing the maximum number of threads.
I tried:
getContext().getParameters().add("maxThreads", "200");
but that does not help. In any case, it seems like the client should be able to make an unlimited number of calls, and increasing maxThreads just ups the limit. It looks like I am not freeing up some resource or disconnecting after each client call, but I do not know how to do so.
The following (small as I could make it) stand alone program demonstrates the problem. It starts a simple server and then a client calls it a bunch of times:
/** You may copy, modify, and re-use this code as you see fit - Jim Irrer */
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import org.restlet.Application;
import org.restlet.Component;
import org.restlet.Request;
import org.restlet.Response;
import org.restlet.Restlet;
import org.restlet.Server;
import org.restlet.data.MediaType;
import org.restlet.data.Method;
import org.restlet.data.Protocol;
import org.restlet.data.Status;
import org.restlet.representation.InputRepresentation;
import org.restlet.representation.Representation;
import org.restlet.resource.ClientResource;
import org.restlet.resource.Directory;
public class SimpleServerPut extends Component implements Runnable {
private static final int PORT = 8080;
private static int readToByteArray(InputStream inputStream, byte[] buf) throws IOException {
int length = 0;
int b;
while ((b = inputStream.read()) != -1) {
buf[length++] = (byte)b;
}
return length;
}
#Override
public void run() {
getContext().getParameters().add("maxThreads", "200");
// Create the HTTP server and listen on port PORT
SimpleServerPut simpleServer = new SimpleServerPut();
Server server = new Server(Protocol.HTTP, PORT, simpleServer);
simpleServer.getClients().add(Protocol.FILE);
// Create an application
Application application = new Application(simpleServer.getContext()) {
#Override
public Restlet createRoot() {
return new Directory(getContext(), "C:");
}
};
// Attach the application to the component and start it
simpleServer.getDefaultHost().attach("/stuff/", application);
try {
server.start();
}
catch (Exception ex) {
ex.printStackTrace();
}
}
#Override
public void handle(Request request, Response response) {
// assume the worst
response.setStatus(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED);
response.setEntity("No no - Bad client! Only do PUTs.", MediaType.TEXT_PLAIN);
try {
if (request.getMethod() == Method.PUT) {
InputStream inputStream = request.getEntity().getStream();
byte[] buf = new byte[64*1024];
int totalLength = readToByteArray(inputStream, buf);
response.setStatus(Status.SUCCESS_OK);
String msg = "Number of bytes received: " + totalLength;
response.setEntity(msg, MediaType.TEXT_PLAIN);
System.out.println("server: " + msg);
return;
}
}
catch (Exception ex) {
ex.printStackTrace();
}
}
private static String callServer() throws IOException {
String urlText = "http://localhost:" + PORT + "/";
ClientResource clientResource = new ClientResource(urlText);
clientResource.setReferrerRef(urlText);
byte[] buf = new byte[1000];
for (int i = 0; i < buf.length; i++) {
buf[i] = (byte)((int)'a' + (i%26));
}
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(buf);
Representation representation = new InputRepresentation(byteArrayInputStream, MediaType.APPLICATION_OCTET_STREAM);
Representation representation2 = clientResource.put(representation);
byte[] responseBuf = new byte[16*1024];
int length = readToByteArray(representation2.getStream(), responseBuf);
Response response = clientResource.getResponse();
Status status = response.getStatus();
return "status: " + status + " message: " + new String(responseBuf, 0, length);
}
// Start server and call it a bunch of times
public static void main(String[] args) throws Exception {
SimpleServerPut simpleServer = new SimpleServerPut();
new Thread(simpleServer).start();
Thread.sleep(200); // cheap trick to make sure that server is running
// make a bunch of client calls
for (int t = 0; t < 100; t++) {
System.out.println("client count: " + (t+1) + " " + callServer());
}
System.exit(0);
}
}
We were only able to solve the problem by stopping the ClientResource's associated client directly (using Restlet version 2.0.15):
Client c = (Client)clientResource.getNext();
try {
c.stop();
} catch (Exception e) {
//handle exception
}
Add a line so that the client releases the resource:
Response response = clientResource.getResponse();
Status status = response.getStatus();
clientResource.release(); // add this line
to the client and everything works. Eventually the server times out if the client dies, but that takes a while.
I've solved my problem downloading the last stable release of the Restlet API
Apparently the .jar the I've been using were an old version where the release() command doesn't make any effect.
Before the update the client log only outputs the start of the client:
Sep 05, 2012 9:50:19 AM org.restlet.engine.http.connector.HttpClientHelper start
INFO: Starting the default HTTP client
Now it are outputing the stop too:
Sep 05, 2012 9:50:19 AM org.restlet.engine.http.connector.HttpClientHelper stop
INFO: Stopping the default HTTP client
In addition to calling ClientResource.release(), you may want to call exhaust() on the representation.
Representation responseRepresentation = response.getEntity();
if (responseRepresentation != null) {
try {
responseRepresentation.exhaust();
} catch (IOException e) {
// handle exception
}
responseRepresentation.release();
}
Related discussion in this thread.