C - TCP Socket (包含 ssl 範例)

使用 TCP 的方式建立 Server 與 Client
(包含 SSL 的使用方式,如:https)

定義


// 是否使用 SSL 傳送
#define HAVE_SSL 1
#define SSL_CA_KEY "/tmp/ssl_ca.key"
#define SSL_CA_CRT "/tmp/ssl_ca.crt"
#define SSL_FILETYPE_PEM "/tmp/ssl_filetype.pem"

#if HAVE_SSL
#define _read(ssl, cmd, len) SSL_read(ssl, cmd, len)
#define _write(ssl, cmd, len) SSL_write(ssl, cmd, len)
#else
#define _read(fd, cmd, len) read(fd, cmd, len)
#define _write(fd, cmd, len) write(fd, cmd, len)
#endif

// l_onoff設定為1時,呼叫close,不論是否還有資料未送出,立即關閉socket
// 或等待l_linger秒數倒數結束,或資料全部送出,再關閉socket
#define HAVE_SO_LINGER 1

#define LISTEN_PORT 11234
#define MAX_CONNECT_CLIENT 10



Server 範例


void sockServer() {
struct sockaddr_in server, client;
int server_s, s_len, c_len;
long client_s, i;
int rv;
struct timeval timeout;
fd_set set;
#if HAVE_SSL
SSL_CTX *ctx_server;
SSL *s;
#else
long s;
#endif
#if HAVE_SO_LINGER
struct linger so_linger;
#endif

#if HAVE_SSL
/* SSL init */
SSL_library_init();
OpenSSL_add_all_algorithms();
SSL_load_error_strings();
if(!(ctx_server = SSL_CTX_new(SSLv23_server_method())))
return;
if (SSL_CTX_use_certificate_file(ctx_server, SSL_CA_CRT, SSL_FILETYPE_PEM) <= 0)
return;
if (SSL_CTX_use_PrivateKey_file(ctx_server, SSL_CA_KEY, SSL_FILETYPE_PEM) <= 0)
return;
if (!SSL_CTX_check_private_key(ctx_server))
return;
#endif
#if HAVE_SO_LINGER
so_linger.l_onoff = 1; // 1:開啟, 0:關閉(使用內核預設)
so_linger.l_linger = 0; // 0
#endif

if ((server_s = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
syslog(1, "(server) socket fail (%d, %s)", errno, strerror(errno));
return;
}

i = 1;
setsockopt(server_s, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i));

s_len = sizeof(server);
server.sin_family = AF_INET;
server.sin_port = htons(LISTEN_PORT);
server.sin_addr.s_addr = INADDR_ANY;
if (bind(server_s, (struct sockaddr *)&server, s_len) < 0) {
syslog(1, "(server) bind fail (%d, %s)", errno, strerror(errno));
return;
}

if (listen(server_s, MAX_CONNECT_CLIENT) < 0) {
syslog(1, "(server) listen fail (%d, %s)", errno, strerror(errno));
return;
}

#if HAVE_SO_LINGER
if(setsockopt(server_s, SOL_SOCKET, SO_LINGER, &so_linger, sizeof(so_linger)) > 0)
syslog(1, "(server) server_s setsockopt fail %d, %s", errno, strerror(errno));
#endif

while(1) {
FD_ZERO(&set);
FD_SET(server_s, &set);
timeout.tv_sec = 30;
timeout.tv_usec = 0;
rv = select(server_s + 1, &set, NULL, NULL, &timeout);
if(rv < 0) { /* error */
break;
} else if(rv == 0) { /* timeout */
continue;
}

c_len = sizeof(client);
if ((client_s = accept(server_s, (struct sockaddr *)&client, (socklen_t *)&c_len)) <= 0) {
syslog(1, "(server) accept fail %d, %s", errno, strerror(errno));
if(errno == 24) { // 24, Too many open files
sleep(1);
continue;
} else {
break;
}
}

{ // 可在這判斷 client IP & Port
syslog(1, "client ip=%x, port=%d", client.sin_addr.s_addr, client.sin_port);
in_addr_t local_ip = inet_addr("192.168.1.1");
if(local_ip == client.sin_addr.s_addr) {
// TODO
}
}

#if HAVE_SO_LINGER
if(setsockopt(client_s, SOL_SOCKET, SO_LINGER, &so_linger, sizeof(so_linger)) > 0)
syslog(1, "(server) client_s setsockopt fail %d, %s", errno, strerror(errno));
#endif

#if HAVE_SSL
s = SSL_new(ctx_server);
SSL_set_fd(s, client_s);
if (SSL_accept(s) > 0) {
#else
s = client_s;
#endif
int readBytes;
char *readData = NULL, *sendData = "responseData";
while((readBytes = my_read((void *)s, &readData)) > 0) {
my_write((void *)s, sendData, strlen(sendData));
free(readData);
readData = NULL;
}
#if HAVE_SSL
SSL_shutdown(s);
}
SSL_free(s);
#endif
}

close(server_s);
#if HAVE_SSL
SSL_CTX_free(ctx_server);
#endif
}



Client 範例


void sockClient(char *ip, int port) {
struct sockaddr_in client;
int client_s, len, retryCount = 0;
char *string = NULL;
#if HAVE_SSL
SSL_CTX *ctx_client;
SSL *s;
#else
long s;
#endif
#if HAVE_SO_LINGER
struct linger so_linger;
#endif

#if HAVE_SSL
/* SSL init */
SSL_library_init();
OpenSSL_add_all_algorithms();
SSL_load_error_strings();
if(!(ctx_client=SSL_CTX_new(SSLv23_client_method())))
return NULL;
#endif
#if HAVE_SO_LINGER
so_linger.l_onoff = 1; // 1:開啟, 0:關閉(使用內核預設)
so_linger.l_linger = 0; // 0
#endif

if ((client_s = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
syslog(1, "(client) socket create fail (%d, %s)", errno, strerror(errno));
// 24, Too many open files
// 110, Connection timed out
return;
}

#if HAVE_SO_LINGER
if(setsockopt(client_s, SOL_SOCKET, SO_LINGER, &so_linger, sizeof(so_linger)) > 0)
syslog(1, "(client) setsockopt fail %d, %s", errno, strerror(errno));
#endif

{
struct timeval timeout;
timeout.tv_sec = 10;
timeout.tv_usec = 0;
setsockopt(client_s, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(timeout));
setsockopt(client_s, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout, sizeof(timeout));
}

len = sizeof(client);
client.sin_family = AF_INET;
client.sin_addr.s_addr = inet_addr(ip); // ip format is "192.168.1.1"
client.sin_port = htons(port);
if (connect(client_s, (struct sockaddr *)&client, len) < 0) {
syslog(1, "(client) connect fail %d, %s", errno, strerror(errno));
return;
}

#if HAVE_SSL
s = SSL_new(ctx_client);
SSL_set_fd(s, client_s);
if (SSL_connect(s) >= 0) {
#else
s = client_s;
#endif
int readBytes;
char *readData = NULL, *sendData = "sendData";
my_write((void *)s, sendData, strlen(sendData));
if((readBytes = my_read((void *)s, &readData)) > 0) {
free(readData);
}

#if HAVE_SSL
SSL_shutdown(s);
}
SSL_free(s);
#endif

close((int)client_s);
#if HAVE_SSL
SSL_CTX_free(ctx_client);
#endif
}



共用函式

my_read 接收字串長度後,再接收字串,並給malloc分配記憶體空間(需要free)
my_write 傳送字串長度 & 字串

int my_read_func(void *_s, char *data, int dataSize)
{
int retryCount = 0;
int readLen = 0;
int len;
#if HAVE_SSL
SSL *s = _s;
#else
long s = _s;
#endif
do {
if((len = _read(s, &data[readLen], dataSize-readLen)) < 0)
break;
readLen += len;
if(!len) usleep(5);
if(!len && ++retryCount >= 1000)
break;
} while(readLen < dataSize);
return readLen;
}
int my_read(void *_s, char **buf)
{
int intLen = sizeof(int);
int dataLen, readLen;
int len = 0;
int retryCount = 0;
char *data;
#if HAVE_SSL
SSL *s = _s;
#else
long s = _s;
struct timeval timeout;
fd_set set;
int rv;

while(1) {
FD_ZERO(&set);
FD_SET(s, &set);
timeout.tv_sec = 30;
timeout.tv_usec = 0;
rv = select(s + 1, &set, NULL, NULL, &timeout);
if(rv < 0) { /* error */
syslog(1, "(my_read) select fail %d, %s", errno, strerror(errno));
return rv;
} else if(rv == 0) { /* timeout */
continue;
} else {
break;
}
}
#endif

// 先接收 4Bytes 的資料長度
dataLen = 0;
data = (char *)&dataLen;
readLen = my_read_func(_s, data, intLen);
if(readLen != intLen)
dataLen = 0;

// 再接收真正的資料
if(dataLen > 0 && (*buf = malloc(dataLen+4))) {
readLen = my_read_func(_s, buf, dataLen);
(*buf)[readLen] = '\0';
}

return readLen;
}

int my_write(void *_s, char *buf, int bufSize)
{
int intLen = sizeof(int);
int dataLen = intLen + bufSize;
int len = -1;
char *data;
#if HAVE_SSL
SSL *s = _s;
#else
long s = (long)_s;
struct timeval timeout;
fd_set set;
int rv;

while(1) {
FD_ZERO(&set);
FD_SET(s, &set);
timeout.tv_sec = 5;
timeout.tv_usec = 0;
rv = select(s + 1, NULL, &set, NULL, &timeout);
if(rv < 0) { /* error */
syslog(1, "(my_write) select fail %d, %s", errno, strerror(errno));
return rv;
} else if(rv == 0) { /* timeout */
continue;
} else {
break;
}
}
#endif

if((data = malloc(dataLen))) {
// 先傳送 4Bytes 的資料長度
*((int *)data) = bufSize;
// 再傳送真正的資料
memcpy(&data[intLen], buf, bufSize);
// 送出資料
len = _write(s, data, dataLen);
free(data);
}

return len;
}

int SocketConnected(long sock)
{
struct tcp_info info;
int len = sizeof(info);

if(sock <= 0)
return 0;

getsockopt(sock, IPPROTO_TCP, TCP_INFO, &info, (socklen_t *)&len);
if((info.tcpi_state == TCP_ESTABLISHED)) { // is connected
return 1;
} else { // is disconnected
return 0;
}
}




建立 socket 的第三個參數應該要填入的值


enum
{
IPPROTO_IP = 0, /* Dummy protocol for TCP. */
IPPROTO_HOPOPTS = 0, /* IPv6 Hop-by-Hop options. */
IPPROTO_ICMP = 1, /* Internet Control Message Protocol. */
IPPROTO_IGMP = 2, /* Internet Group Management Protocol. */
IPPROTO_IPIP = 4, /* IPIP tunnels (older KA9Q tunnels use 94). */
IPPROTO_TCP = 6, /* Transmission Control Protocol. */
IPPROTO_EGP = 8, /* Exterior Gateway Protocol. */
IPPROTO_PUP = 12, /* PUP protocol. */
IPPROTO_UDP = 17, /* User Datagram Protocol. */
IPPROTO_IDP = 22, /* XNS IDP protocol. */
IPPROTO_TP = 29, /* SO Transport Protocol Class 4. */
IPPROTO_DCCP = 33, /* Datagram Congestion Control Protocol. */
IPPROTO_IPV6 = 41, /* IPv6 header. */
IPPROTO_ROUTING = 43, /* IPv6 routing header. */
IPPROTO_FRAGMENT = 44, /* IPv6 fragmentation header. */
IPPROTO_RSVP = 46, /* Reservation Protocol. */
IPPROTO_GRE = 47, /* General Routing Encapsulation. */
IPPROTO_ESP = 50, /* encapsulating security payload. */
IPPROTO_AH = 51, /* authentication header. */
IPPROTO_ICMPV6 = 58, /* ICMPv6. */
IPPROTO_NONE = 59, /* IPv6 no next header. */
IPPROTO_DSTOPTS = 60, /* IPv6 destination options. */
IPPROTO_MTP = 92, /* Multicast Transport Protocol. */
IPPROTO_ENCAP = 98, /* Encapsulation Header. */
IPPROTO_PIM = 103, /* Protocol Independent Multicast. */
IPPROTO_COMP = 108, /* Compression Header Protocol. */
IPPROTO_SCTP = 132, /* Stream Control Transmission Protocol. */
IPPROTO_UDPLITE = 136, /* UDP-Lite protocol. */
IPPROTO_RAW = 255, /* Raw IP packets. */
IPPROTO_MAX
};

/* Standard well-known ports. */
enum
{
IPPORT_ECHO = 7, /* Echo service. */
IPPORT_DISCARD = 9, /* Discard transmissions service. */
IPPORT_SYSTAT = 11, /* System status service. */
IPPORT_DAYTIME = 13, /* Time of day service. */
IPPORT_NETSTAT = 15, /* Network status service. */
IPPORT_FTP = 21, /* File Transfer Protocol. */
IPPORT_TELNET = 23, /* Telnet protocol. */
IPPORT_SMTP = 25, /* Simple Mail Transfer Protocol. */
IPPORT_TIMESERVER = 37, /* Timeserver service. */
IPPORT_NAMESERVER = 42, /* Domain Name Service. */
IPPORT_WHOIS = 43, /* Internet Whois service. */
IPPORT_MTP = 57,

IPPORT_TFTP = 69, /* Trivial File Transfer Protocol. */
IPPORT_RJE = 77,
IPPORT_FINGER = 79, /* Finger service. */
IPPORT_TTYLINK = 87,
IPPORT_SUPDUP = 95, /* SUPDUP protocol. */

IPPORT_EXECSERVER = 512, /* execd service. */
IPPORT_LOGINSERVER = 513, /* rlogind service. */
IPPORT_CMDSERVER = 514,
IPPORT_EFSSERVER = 520,

/* UDP ports. */
IPPORT_BIFFUDP = 512,
IPPORT_WHOSERVER = 513,
IPPORT_ROUTESERVER = 520,

/* Ports less than this value are reserved for privileged processes. */
IPPORT_RESERVED = 1024,

/* Ports greater this value are reserved for (non-privileged) servers. */
IPPORT_USERRESERVED = 5000
};

沒有留言:

張貼留言