I have a server written in C# using Sockets:
/*
Richard D. Grant
R.grant.jr.122193#gmail.com
-Contact for details-
*/
using System;
using System.Net;
using System.Net.Sockets;
using System.Collections.Generic;
using System.Text;
using System.Security.Cryptography;
using System.Threading;
namespace SERVER{
static class Application{
private static readonly List<Socket> _client_list = new List<Socket>();
private const ushort _port = 8080, _buffer_len = 1024;
private static byte[] _buf = new byte[_buffer_len];
private static string key;
static private string guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
private static Boolean exiting = false;
static int Main(){
Console.Title = "Server Application";
Socket server_socket = StartServer();
while(!exiting){
}
return CloseServer(server_socket);
}
private static Socket StartServer(){
IPEndPoint IPE = new IPEndPoint(IPAddress.Any, _port); // 192.168.0.14:80
Socket server_socket = new Socket(IPE.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
server_socket.Bind(IPE);
server_socket.Listen(128);
server_socket.BeginAccept(AcceptCallback, server_socket);
Console.WriteLine("Server Succeeded.");
return server_socket;
}
private static void AcceptCallback(IAsyncResult AR){
Console.WriteLine("accepting...");
Socket server_socket = (Socket)AR.AsyncState;
Socket client_socket = server_socket.EndAccept(AR);
client_socket.BeginReceive(_buf, 0, _buffer_len, SocketFlags.None, RecCallback, client_socket);
server_socket.BeginAccept(AcceptCallback, server_socket);
}
private static void RecCallback(IAsyncResult AR){
Console.WriteLine("Recieving");
Socket client_socket = (Socket)AR.AsyncState;
int rec_len = client_socket.EndReceive(AR);
byte[] rec_buf = new byte[rec_len];
Array.Copy(_buf, rec_buf, rec_len);
string rec_text = Encoding.UTF8.GetString(rec_buf);
if(!_client_list.Contains(client_socket)){
_client_list.Add(client_socket);
key = rec_text.Replace("ey:", "`")
.Split('`')[1] // dGhlIHNhbXBsZSBub25jZQ== \r\n .......
.Replace("\r", "").Split('\n')[0] // dGhlIHNhbXBsZSBub25jZQ==
.Trim();
var test1 = AcceptKey(ref key);
var newLine = "\r\n";
var response = "HTTP/1.1 101 Switching Protocols" + newLine
+ "Upgrade: websocket" + newLine
+ "Connection: Upgrade" + newLine
+ "Sec-WebSocket-Accept: " + test1 + newLine + newLine
;
client_socket.Send(Encoding.UTF8.GetBytes(response));
client_socket.Send("server received");
}else{
Console.WriteLine(rec_text);
}
_buf = new byte[_buffer_len];
client_socket.BeginReceive(_buf, 0, _buffer_len, SocketFlags.None, RecCallback, client_socket);
}
private static void client_disconnect(Socket soc){
soc.Shutdown(SocketShutdown.Both);
soc.Close();
}
private static ushort CloseServer(Socket server_socket){
foreach(Socket client in _client_list){
client_disconnect(client);
}
server_socket.Close();
return 0;
}
private static string AcceptKey(ref string key){
string longKey = key + guid;
byte[] hashBytes = ComputeHash(longKey);
return Convert.ToBase64String(hashBytes);
}
static SHA1 sha1 = SHA1CryptoServiceProvider.Create();
private static byte[] ComputeHash(string str){
return sha1.ComputeHash(Encoding.UTF8.GetBytes(str));
}
}
}
And i have the HTML5 websocket client:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<script type="text/javascript">
function connect() {
var ws = new WebSocket("ws://localhost:8080/service");
ws.onopen = function () {
ws.send("Hello World"); // I WANT TO SEND THIS MESSAGE TO THE SERVER!!!!!!!!
console.log("opened!");
};
ws.onmessage = function (evt) {
console.log("About to receive data");
var received_msg = evt.data;
console.log("Message received = "+received_msg);
};
ws.onclose = function () {
// websocket is closed.
console.log("Connection is closed...");
};
};
</script>
</head>
<body style="font-size:xx-large" >
<div>
Click here to start</div>
</body>
</html>
HTML5 websocket requires a handshake, and the server deals with that accordingly.
client_socket.send(Encoding.UTF8.GetBytes("send"));
Does not trigger html5 onmessage
ws.send("send");
returns scrambled text to the server, but it is sent successfully.
The server deals with the handshake, but it does not deal with the packet frame after that. The message is not sent back in plain text and requires some processing.
How can I send and receive WebSocket messages on the server side?
This details the frame specification. For example, the first byte is always 129, the seconds has to do with the length. There is an example C# method to decode the frame on the link provided.
I wanted to post the source code to my solution, though i would like to give credit to AlexH for pointing me in the correct location.
private static byte[] encode(string str){
List<byte> lb = new List<byte>();
lb.Add(0x81);//129 to represent text frame
lb.Add((byte)str.Length);//2nd byte represents the length of the string
lb.AddRange(System.Text.Encoding.UTF8.GetBytes(str));
return lb.ToArray();
}
Frame:
+---------------------------------+
| FRAME TYPE | DATA LENGTH | DATA |
+---------------------------------+
Related
I'm following this tutorial : https://www.baeldung.com/websockets-spring
I tested the app and it works perfectly when running on the embedded-tomcat server. However, when I try to deploy and run the same app on an external tomcat server it breaks, because instead of the URL being
localhost:8080/chat
it becomes
myhostIP:port/spring-boot-web-jsp/chat
So I modified the javascript file adding /spring-boot-web-jsp in front of the existing URLs. When I run the webapp the sockets connect successfully and send data. However now my Spring MVC Controller doesn't work.
My javascript :
var stompClient = null;
function setConnected(connected) {
document.getElementById('connect').disabled = connected;
document.getElementById('disconnect').disabled = !connected;
document.getElementById('conversationDiv').style.visibility
= connected ? 'visible' : 'hidden';
document.getElementById('response').innerHTML = '';
}
function connect() {
var socket = new SockJS('/spring-boot-web-jsp-1.0/chat');
stompClient = Stomp.over(socket);
stompClient.connect({}, function(frame) {
setConnected(true);
console.log('Connected: ' + frame);
stompClient.subscribe('/spring-boot-web-jsp-1.0/topic/messages', function(messageOutput) {
showMessageOutput(JSON.parse(messageOutput.body));
});
});
}
function disconnect() {
if(stompClient != null) {
stompClient.disconnect();
}
setConnected(false);
console.log("Disconnected");
}
function sendMessage() {
var from = document.getElementById('from').value;
var text = document.getElementById('text').value;
stompClient.send("/spring-boot-web-jsp-1.0/app/chat", {},
JSON.stringify({'from':from, 'text':text}));
}
function showMessageOutput(messageOutput) {
var response = document.getElementById('response');
var p = document.createElement('p');
p.style.wordWrap = 'break-word';
p.appendChild(document.createTextNode(messageOutput.from + ": "
+ messageOutput.text + " (" + messageOutput.time + ")"));
response.appendChild(p);
}
My Controller :
#MessageMapping("/chat")
#SendTo("/topic/messages")
public OutputMessage send(Message message) throws Exception {
String time = new SimpleDateFormat("HH:mm").format(new Date());
return new OutputMessage(message.getFrom(), message.getText(), time);
}
My message broker :
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/chat");
registry.addEndpoint("/chat").withSockJS();
}
}
I've tried modifying the Controller to :
#MessageMapping("app/chat")
#SendTo("/topic/messages")
public OutputMessage send(Message message) throws Exception {
String time = new SimpleDateFormat("HH:mm").format(new Date());
return new OutputMessage(message.getFrom(), message.getText(), time);
}
#MessageMapping("spring-boot-web-jsp-1.0/app/chat")
#SendTo("spring-boot-web-jsp-1.0/topic/messages")
public OutputMessage send(Message message) throws Exception {
String time = new SimpleDateFormat("HH:mm").format(new Date());
return new OutputMessage(message.getFrom(), message.getText(), time);
}
and a bunch of other variations but none of them work.
How can I modify the Controller and javascript file to work when testing through external Apache Tomcat as well as embedded (setting a relative URL of some sort)? And how can I get this to work properly on the external Tomcat?
Remove your tomcat/webapps/ROOT directory
Rename your final jar/war/ear file to ROOT.jar/war/ear
Deploy it on tomcat
Tomcat will deploy your app under root directory localhost:8080/chat
So I'm trying to make a webservice that allows someone to obtain data from a server. Right now, the server I'm using is written using java's HttpServer class. I plan to make the server accessible using fetch() in javascript, but it's not working.
When I was first testing out my server, I used Apache's HttpComponents library, and that client(written in java). was able to receive the test json that came from my server. However, when I used fetch() on my javascript client, nothing is received when I console.log everything. It doesn't make sense to me why it would work in Java, but not javascript. Does anyone know why this is not working? Am I just doing the javascript part wrong, and it does actually work? Thanks!
Code snippets for reference:
java server:
public class Main {
private static final int PORT = 1337;
private static final int BACKLOG = 1;
public static void main(String[] args) {
try {
HttpServer server = HttpServer.create(new InetSocketAddress(PORT), BACKLOG);
System.out.print("started on" + PORT);
HttpContext context = server.createContext("/ex", new Handler());
server.start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
class Handler implements HttpHandler {
#Override
public void handle(HttpExchange he) throws IOException {
System.out.println("handled");
JSONObject obj = new JSONObject();
obj.put("name", "value");
obj.put("num", new Integer(100));
obj.put("balance", new Double(1000.21));
obj.put("is_vip", new Boolean(true));
obj.put("array", new int[]{1, 2, 3});
String response = obj.toJSONString();
he.sendResponseHeaders(200, response.length());
he.getResponseBody().write(response.getBytes());
}
}
java client:
public class PostTest {
public static void main(String[] args) throws IOException {
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpPost httppost = new HttpPost("http://localhost:1337/ex");
CloseableHttpResponse response = httpClient.execute(httppost);
System.out.println("STATUS LINE");
System.out.println(response.getStatusLine().toString());
System.out.println("HEADER");
Header[] h = response.getAllHeaders();
for(int i = 0; i < h.length; i++) {
System.out.println(h[i]);
}
System.out.println("ENTITY.CONTENT");
try(BufferedReader br = new BufferedReader(new InputStreamReader(response.getEntity().getContent(), "UTF-8"))) {
String s;
while((s = br.readLine()) != null) {
System.out.println(s);
}
}
}
}
Javascript:
var init = {mode: "no-cors"};
async function f() {
return fetch("http://localhost:1337/ex", init).then(res => res.text()).then(posts => console.log(posts));
}
f();
I'm trying to send binary data between server (C#) application and client (js-application -- made by WebSocket). Connection between server and client is established and handshake is OK. Messages from client are receiving by server, but when I'm trying to send binary data to client, the event "onmessage" doesn't work.
This is fragments of my C# code. Sending binary data in "sendFile" function.
class Listener
{
private IPAddress ip;
private int port;
private TcpListener server;
private TcpClient client;
private NetworkStream stream;
private bool isSuccHandshaked;
public Listener()
{
ip = IPAddress.Loopback;
port = 8080;
server = new TcpListener(ip, port);
isSuccHandshaked = false;
}
private void makeHandshake()
{
//...
}
private String decodeMessage(Byte[] bytes)
{
//...
}
private void sendFile()
{
Byte[] dataToSend = File.ReadAllBytes("test.txt");
stream.Write(datatosend, 0, datatosend.Length);
stream.Flush();
}
public void startListen()
{
server.Start();
Console.WriteLine("Server has started on {0}. Port: {1}. {2}Waiting for a connection...", ip, port, Environment.NewLine);
client = server.AcceptTcpClient();
Console.WriteLine("A client connected.");
stream = client.GetStream();
while (!isSuccHandshaked)
{
makeHandshake();
}
while (true)
{
if (client.Available > 0)
{
Byte[] bytes = new Byte[client.Available];
stream.Read(bytes, 0, bytes.Length);
String message = decodeMessage(bytes);
sendFile();
}
}
}
}
}
and js-code:
var address = 'ws://localhost:8080';
var socket = new WebSocket( address );
socket.binaryType = "arraybuffer";
socket.onopen = function () {
alert( 'handshake successfully established. May send data now...' );
socket.send( "Aff" );
};
socket.onclose = function () {
alert( 'connection closed' );
};
socket.onmessage = function ( evt ) {
console.log( "Receive message!" );
console.log( "Got ws message: " + evt.data );
}
Maybe there is some peculiar properties in receiving data with WebSocket protocol? Wich useful approaches to send binary data from C# code to js you can recommend?
A websocket is not a raw TCP socket. It uses HTTP negotiation and its own framing protocol you have to comply with. If you are interested in writing your own server in C# take a look at this.
However if you only want to use them, you can either use the default Microsoft implementation with IIS, or you can use one of the many standalone websockets components. I maintain one named WebSocketListener.
I'm learning how to get my C# program communicate with browser.
I'm using TCP in C# and WebSocket in my HTML5 browser.
C#
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
using System.IO;
namespace ShouldWork
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
TCPServer server = new TCPServer();
System.Threading.Thread obj_thread = new System.Threading.Thread(server.startServer);
obj_thread.Start();
}
private void textBox1_TextChanged(object sender, EventArgs e)
{
}
private void button2_Click(object sender, EventArgs e)
{
TcpClient client = new TcpClient("127.0.0.1", 7000);
NetworkStream ns = client.GetStream();
byte[] data_tosend = createDataPacket(Encoding.UTF8.GetBytes(tx_data_send.Text));
ns.Write(data_tosend, 0, data_tosend.Length);
}
private byte[] createDataPacket(byte[] data)
{
byte[] initialize = new byte[1];
initialize[0] = 2;
byte[] separator = new byte[1];
separator[0] = 4;
byte[] datalength = Encoding.UTF8.GetBytes(Convert.ToString(data.Length));
MemoryStream ms = new MemoryStream();
ms.Write(initialize, 0, initialize.Length);
ms.Write(datalength, 0, datalength.Length);
ms.Write(separator, 0, separator.Length);
ms.Write(data, 0, data.Length);
return ms.ToArray();
}
}
class TCPServer
{
TcpListener listener;
public TCPServer()
{
listener = new TcpListener(IPAddress.Any, 7000);
}
public void startServer()
{
listener.Start();
while (true)
{
TcpClient client = listener.AcceptTcpClient();
NetworkStream ns = client.GetStream();
if(ns.ReadByte() == 2)
{
byte[] recv_data = readStream(ns);
Form1.ActiveForm.Invoke(new MethodInvoker(delegate
{
((TextBox)Form1.ActiveForm.Controls.Find("tx_recv_data", true)[0]).Text = Encoding.UTF8.GetString(recv_data);
}));
}
}
}
public byte[] readStream(NetworkStream ns)
{
byte[] data_buff = null;
int b = 0;
String buff_length = "";
while ((b = ns.ReadByte()) != 4)
{
buff_length += (char)b;
}
int data_length = Convert.ToInt32(buff_length);
data_buff = new byte[data_length];
int byte_read = 0;
int byte_offset = 0;
while (byte_offset < data_length)
{
byte_read = ns.Read(data_buff, byte_offset, data_length - byte_offset);
byte_offset += byte_read;
}
return data_buff;
}
}
}
UI
When user clicks Start Server button, the program would then start the TCPServer. Then we can enter some text in the second textbox and my code will then process it and display the message on the first textbox.
Now I'd like to move the client part to the browser, so I created a simple web app and test out connection.
Code snippet
<!DOCTYPE html>
<html>
<head>
<title>Socket demo</title>
</head>
<body>
<button type="button" onclick="testWebSocket()">Connect to C# server</button><br><br>
<script language="javascript" type="text/javascript">
function testWebSocket(){
var socket = new WebSocket('ws://127.0.0.1:7000');
console.log("ssss");
socket.onopen = function(){
console.log("on open");
socket.send("Hello");
}
socket.onclose = function(evt){
console.log("on close");
}
socket.onerror = function(evt){
console.log("on error");
console.log(evt.data);
}
}
</script>
</body>
</html>
Here is what's wrong..... the onopen function doesn't seems to work at all, chrome console does display "sss" and that's it....
Is there something wrong with the approach I'm using?
As indicated in the comments, WebSocket uses its own framing schema.
Take a look to this link: https://hpbn.co/websocket/, specially the part where the framing is explained: https://hpbn.co/websocket/#binary-framing-layer
The reason why the connection never gets open, is because it is unable of completing the handshake : https://hpbn.co/websocket/#http-upgrade-negotiation
I want to know how to define the subscriber path.
For instance, declaration of subscribing path
stompClient.subscribe("/topic/simplemessagesresponse", function(servermessage) {
Why there are two parts 'topic' and 'simplemessageresponse' .. what they refere. How many such domain parts can be there and why ? My question is on not only for the client side, but also server side . SimpMessagingTemplate.convertAndSend("/topic/simplemessagesresponse", "Message to client");
There are tutorials showing the websocket server and client samples. But no enough details of rules to declare the subscriber path and how the subscriber path could be found.
What are the dependencies to change the path when it is declared in server and client side. I think another similar question is raised because of the a location change of a page where the websocket client is written.
Quoting the STOMP spec documentation:
Note that STOMP treats this destination as an opaque string and no
delivery semantics are assumed by the name of a destination. You
should consult your STOMP server's documentation to find out how to
construct a destination name which gives you the delivery semantics
that your application needs.
That means that destination semantics is broker specific:
For RabbitMQ: check out the Destinations section under the STOMP
plugin documentation- http://www.rabbitmq.com/stomp.html For
For ActiveMQ: check out the Working with Destinations with Stomp -
https://activemq.apache.org/stomp.html
I have implemented the websocket stomp by following this blog.
I replaced #SendTo by SimpMessagingTemplate.
Here is my sample ChatController
#Autowired
private SimpMessagingTemplate simpMessagingTemplate;
#MessageMapping("/dualchart")
#ResponseBody
public void dualchat(MessageDTO message) {
// forward message to destination
String destination = "/topic/dualchat/" + message.getToUser();
simpMessagingTemplate.convertAndSend(destination, message);
}
MessageDTO
#JsonIgnoreProperties
public class MessageDTO extends BaseModel {
private String fromUser;
private String toUser;
private String message;
public String getFromUser() {
return fromUser;
}
public void setFromUser(String fromUser) {
this.fromUser = fromUser;
}
public String getToUser() {
return toUser;
}
public void setToUser(String toUser) {
this.toUser = toUser;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
Web Socket Config
<websocket:message-broker application-destination-prefix="/app">
<websocket:stomp-endpoint path="/dualchat">
<websocket:sockjs />
</websocket:stomp-endpoint>
<websocket:simple-broker prefix="/topic" />
</websocket:message-broker>
Javascript
var socket = new SockJS("/starter.web.admin/dualchat");
var stompClient = Stomp.over(page.socket);
stompClient.connect({}, socketJsConnectedCallback, socketJsErrorCallback);
function socketJsConnectedCallback() {
var myId = "111"; // replace this Id
stompClient.subscribe('/topic/dualchat/' + myId, function(message) {
console.log("you reveived a message::::::::::" + JSON.stringify(message));
// you have message, and you can do anything with it
});
}
function socketJsErrorCallback(error){console.log(error);}
function sendMessage(message) {
var data = {
toUser : "1",
message : message
}
stompClient.send("/app/dualchat", {}, JSON.stringify(data );
}
Hope this will help next search...