需求:
最近公司讓實現一個工具,通過這個工具可以與后台服務器進程建立連接並發送數據包。這個工具實際上相當於將游戲客戶端的網絡部分的功能剝離出來。利用這個工具可以達到的目的非常多,其中包括模擬發包探測后台進程是否正常運行,模擬發包檢驗后台代碼魯棒性以及對抄襲我們公司游戲的競爭對手發起DDos攻擊(估計是最后一個原因領導才想要做的吧,給別人平淡的生活來點刺激)。
過程分析:
為了用c語言實現該工具,需要對當前項目的技術棧有個了解。

其中客戶端和Nginx是通過TCP建立連接,然后在此基礎上利用openSSL將連接升級為加密連接。最后在此連接上利用http協議握手將此連接升級為websocket協議。
Ngnix和后台服務器進程是通過websocket明文通信的。
過程實現:
通過上述的過程分析就能知道解決以下問題就能將需求完整實現:
1.配置Nginx反向代理http:

其中server塊就是虛擬主機,在里面設置監聽哪個端口。Location塊是根據用戶請求的URI來進行不同的定位,定位到不同的處理方式上,匹配成功即進行相關的操作。
server { #限制單IP連接數 limit_conn perip 100; #監聽端口 listen 443 ssl; #listen 443; #配置域名為servername server_name xxxxxxx.net; #配置ssl,其中有加密算法和證書 ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4; ssl_certificate /usr/local/ssl/Nginx/xxxxxx.pem; ssl_certificate_key /usr/local/ssl/Nginx/xxxxx.key; ssl_session_timeout 60m; ssl_prefer_server_ciphers on; location / { # 判斷HTTP的Upgrade協議頭,當為websocket時,轉給ws,反之轉給http if ( $http_upgrade ~* websocket$ ) { proxy_pass http://127.0.0.1:1001; } proxy_pass http://127.0.0.1:80; proxy_http_version 1.1; proxy_read_timeout 3600s; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'Upgrade'; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $remote_addr; } }
其中location的規則中匹配了兩條規則,其中一條是如果$http_upgrade字段中包含websocket字符串,那么將請求轉發到1001端口的進程中。在這里是對http升級到websockt協議時起的作用。當升級到websocket協議以后,后續的數據包依然會轉發到1001端扣的進程中。即使數據包中已經沒有包含websocket字符串了。
2.調用哪個openSSL接口實現加密。
依據Nginx配置里面配置的:
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
SSL_library_init(); SSLeay_add_ssl_algorithms(); SSL_load_error_strings(); const SSL_METHOD *meth = TLSv1_2_client_method(); SSL_CTX *ctx = SSL_CTX_new (meth); ssl = SSL_new (ctx); if (!ssl) { printf("Error creating SSL.\n"); log_ssl(); return -1; } sock = SSL_get_fd(ssl); SSL_set_fd(ssl, s); int err = SSL_connect(ssl); if (err <= 0) { printf("Error creating SSL connection. err=%x\n", err); log_ssl(); fflush(stdout); return -1; } printf ("SSL connection using %s\n", SSL_get_cipher (ssl));
3.如何利用http將協議升級為websocket協議
客戶端拼裝一個頭發送給服務端,例如一個http request header:
GET / HTTP/1.1 Upgrade:websocket Connection: Upgrade Host: xxx.xxx.net Sec-WebSocket-Key: J2BJc+GQuSw34hi2TjyVpg== Sec-WebSocket-Version: 13
4.http的額外知識
1.HTTP是無連接:無連接的含義是限制每次連接只處理一個請求。服務器處理完客戶的請求,並收到客戶的應答后,即斷開連接。采用這種方式可以節省傳輸時間。
2.客戶端http請求的格式:


3.http請求方式:常見: GET, POST方法
4.http常見狀態碼:
404:資源不存在。
101:切換協議(Websockt切換成功以后會返回)
500:內部服務器錯誤。
200:請求成功
源碼:
#include <stdio.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <openssl/ssl.h> #include <openssl/err.h> #include <string.h> using namespace std; SSL *ssl; int sock; int RecvPacket() { int len=100; char buf[1000000]; do { len=SSL_read(ssl, buf, 100); buf[len]=0; printf("%s\n",buf); // fprintf(fp, "%s",buf); } while (len > 0); if (len < 0) { int err = SSL_get_error(ssl, len); if (err == SSL_ERROR_WANT_READ) return 0; if (err == SSL_ERROR_WANT_WRITE) return 0; if (err == SSL_ERROR_ZERO_RETURN || err == SSL_ERROR_SYSCALL || err == SSL_ERROR_SSL) return -1; } } int SendPacket(const char *buf) { int len = SSL_write(ssl, buf, strlen(buf)); if (len < 0) { int err = SSL_get_error(ssl, len); switch (err) { case SSL_ERROR_WANT_WRITE: return 0; case SSL_ERROR_WANT_READ: return 0; case SSL_ERROR_ZERO_RETURN: case SSL_ERROR_SYSCALL: case SSL_ERROR_SSL: default: return -1; } } } void log_ssl() { int err; while (err = ERR_get_error()) { char *str = ERR_error_string(err, 0); if (!str) return; printf(str); printf("\n"); fflush(stdout); } } int main(int argc, char *argv[]) { int s; s = socket(AF_INET, SOCK_STREAM, 0); if (s < 0) { printf("Error creating socket.\n"); return -1; } struct sockaddr_in sa; memset (&sa, 0, sizeof(sa)); sa.sin_family = AF_INET; sa.sin_addr.s_addr = inet_addr("xxx.xxx.xxx.xxx"); //nginx所在服務器的ip sa.sin_port = htons (443); socklen_t socklen = sizeof(sa); if (connect(s, (struct sockaddr *)&sa, socklen)) { printf("Error connecting to server.\n"); return -1; } SSL_library_init(); SSLeay_add_ssl_algorithms(); SSL_load_error_strings(); const SSL_METHOD *meth = TLSv1_2_client_method(); SSL_CTX *ctx = SSL_CTX_new (meth); ssl = SSL_new (ctx); if (!ssl) { printf("Error creating SSL.\n"); log_ssl(); return -1; } sock = SSL_get_fd(ssl); SSL_set_fd(ssl, s); int err = SSL_connect(ssl); if (err <= 0) { printf("Error creating SSL connection. err=%x\n", err); log_ssl(); fflush(stdout); return -1; } printf ("SSL connection using %s\n", SSL_get_cipher (ssl)); char *request = char *request = "GET / HTTP/1.1\r\nUpgrade:websocket\r\nConnection: Upgrade\r\nHost: xxxxxxx(服務器域名)\r\nSec-WebSocket-Key: J2BJc+GQuSw34hi2TjyVpg==\r\n\r\n"; SendPacket(request); RecvPacket(); return 0; }
編譯鏈接的時候需要用到openssl庫和加密算法庫:
g++ main.c -lssl -lcrypto
