
웹소켓 접속과정은 다음과 같습니다.
순서대로 C#으로 코드를 작성해 보겠습니다
private static TcpListener listener;
static void Main(string[] args)
{
listener = new TcpListener(IPAddress.Loopback, 8080);
listener.Start();
Listen();
}
Tcp 네트워크 클라이언트에서 연결을 수신합니다.
private static void Listen()
{
while (true)
{
TcpClient client = listener.AcceptTcpClient();
NetworkStream ns = client.GetStream();
Request(ns);
}
}
//보류 중인 연결을 받아들입니다.
//그 후 클라이언트가 보내는 요청 헤더를 분석하는 Request메서드를 정의합니다.
private static void Request(NetworkStream ns)
{
byte[] httpHeader = new byte[2048];
ns.Read(httpHeader, 0, httpHeader.Length);
string header = Encoding.Default.GetString(httpHeader).Trim('\0');
if (header.ToLower().Contains("get / http/1.1"))
{
if (header.ToLower().Contains("upgrade: websocket"))
{
string[] elements = header.Split("\r\n".ToCharArray());
foreach (string element in elements)
{
if (element.ToLower().Contains("sec-websocket-key"))
{
string sec_key = element.Split(" ".ToCharArray())[1];
Response(ns, sec_key);
break;
}
}
}
else
{
Response(ns);
}
}
}
//Request 메서드에서는 요청헤더에 웹소켓 프로토콜 스위칭 요청이 없을 경우 미리 준비된 html 파일을 웹브라우저에 띄어주는 응답 헤더 메서드와 웹소켓 응답헤더를 정의합니다.
private static string html_file = "client.html";
private static void Response(NetworkStream ns)
{
FileInfo fi = new FileInfo(html_file);
byte[] data = new byte[fi.Length];
FileStream fs = new FileStream(html_file, FileMode.Open);
fs.Read(data, 0, data.Length);
fs.Close();
string header = "HTTP/1.1 200 OK\r\n";
header += "Content-Length: " + data.Length.ToString() + "\r\n";
header += "\r\n";
ns.Write(Encoding.Default.GetBytes(header), 0, Encoding.Default.GetBytes(header).Length);
ns.Write(data, 0, data.Length);
}
//html 파일을 웹브라우저에게 띄어주는 응답헤더를 보내주는 메서드
private static string key_value = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
private static void Response(NetworkStream ns, string sec_key)
{
Console.WriteLine("Response..\n");
SHA1 sha = new SHA1CryptoServiceProvider();
byte[] key = Encoding.Default.GetBytes(sec_key + key_value);
byte[] hash = sha.ComputeHash(key);
string accept_key = Convert.ToBase64String(hash);
string header = "HTTP/1.1 101 Switching Protocols\r\n"
+ "Upgrade: WebSocket\r\n" + "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Accept: " + accept_key + "\r\n"
+ "\r\n";
byte[] byte_header = Encoding.Default.GetBytes(header);
ns.Write(byte_header, 0, byte_header.Length);
}
// 웹소켓 프로토콜 스위칭 응답 헤더
//스위칭 프로토콜을 하기 위해서는 핸드쉐이크 과정이 필요한데 웹소켓 핸드쉐이크 과정은
//요청헤더에 포함된 sec-websocket-key에 있는 키값 뒤에 미리 정의된 문자열인 "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"를 더한후 SHA-1 알고리즘을 통해 암호화를 한 값을 Base64로 인코딩 한 후 그 값을 응답헤더의 ""Sec-WebSocket-Accept" 에 에 넣어주면 웹소켓의 연결이 완료됩니다.
추가.
//bin/debug/client.html
<!DOCTYPE html>
<html>
<meta charset="utf-8"/>
<head>
<script type="text/javascript">
function connect() {
var ws = new WebSocket("ws://127.0.0.1:8080/"); //웹소켓 연결
ws.onopen = function () { //웹 소켓이 열려 있을 경우 해당 함수 실행
alert("About to send data");
};
ws.onclose = function () { //웹 소켓이 닫혀 있을 경우 해당 함수 실행
alert("닫힌듯");
};
};
</script>
</head>
<body>
<input type="button" value="클릭" [안내]태그제한으로등록되지않습니다-xxonclick="connect()"/> //버튼에 클릭 이벤트
</body>
</html>