运行环境:Linux2.6以上
文件说明:tcputil.c --------------- TCP多线程服务框架实现
tcputil.h --------------- 公开函数声明
使用说明:
发送消息必须采用固定的(消息大小,消息体)这种流边界方式,其中消息大小是uint32_t类型,并且是网络字节序。
直接调用start(监听IP, 监听端口,自定义消息处理函数)即可;主要是提供自定义的消息处理函数,原型为:
int msg_handler(int socket, void* buf, uint32_t n),其中: socket-接收消息的socket,buf-消息体内存,n-消息体长度。
几个关键点:
(1)发送和接收n个字节的方法,在readn()和writen()函数实现;
(2)向派生线程传递参数时,注意并发导致的同步问题,参见start()函数中的传参实现;
(3)遵循“malloc和free要成对存在于同一个函数中”,但是(2)违反了这个原则,是否有更好的解决方案?
(4)采用了回调函数机制(类似C#中的事件)来让库使用着自定义消息处理函数;(这也是为了遵循(3)采取的策略);
(5)TCP流边界,采取了(消息大小,消息体)的方式,其中消息大小为4字节无符号整数。
存在问题:
(1)性能问题,目前是直接分配与消息体大小同样的内存来接收消息体;
(2)大消息问题,目前消息不能大于int32_t的最大值,对于大数据量传送,请在消息体内实现自定义的用户消息格式来把大数据分块传送;
源码:tcputil.h
#ifndef TCPUTIL_H #define TCPUTIL_H #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <sys/types.h> ssize_t writen(int fd, void* buf, size_t n); ssize_t recvn(int fd, void* buf, size_t n); /*callback function called after received one message, 0-success, -1-error*/ typedef int (*message_handler)(int socket, void * buf, uint32_t size); int start(uint32_t listenip, uint16_t listenport, message_handler handler); #endif
/************************************************** * * $description: collection of functions * $author: smstong * $date: Tue Apr 16 10:24:22 CST 2013 * * ************************************************/ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <sys/types.h> /************************************************** * func: receive n bytes from socket except an error * params: fd - socket handle * buf - memory space to write * n - size of buf * return: -1 - error; * >=0 - actually retceived bytes *************************************************/ ssize_t recvn(int fd, void* buf, size_t n) { char* ptr = (char*)buf; // position pointer size_t left = n; // bytes left to read while(left > 0) { size_t nread = read(fd, ptr, left); if(nread<0) { if(errno==EINTR) { // an error occured nread = 0; } else { return -1; } } else if(nread==0) { //normally disconnect, FIN segment received break; } else { left -= nread; ptr += nread; } } return (n-left); } /******************************************************** * function: write n bytes to socket except error * params: fd - socket hanle * buf - src memory * n - bytes to write * return: -1 - error * >=0 - bytes actually written * ******************************************************/ ssize_t writen(int fd, void* buf, size_t n) { char* ptr = (char*)buf; size_t left = n; while(left > 0) { size_t nwrite = write(fd, ptr,left); if(nwrite<0) { if(errno==EINTR) { nwrite = 0; } else { return -1; } } else if(nwrite==0) { break; } else { left -= nwrite; ptr += nwrite; } } return (n-left); } static void * thread_f(void *); //thread function typedef int (*message_handler)(int, void *, uint32_t); // callback function called after received one message /************************************************************* * * one thread per connection frameset * * ***********************************************************/ // thread function's args struct thread_arg { int socket; message_handler msg_handler; }; int start(uint32_t listenip, uint16_t listenport, message_handler handler) { int listenfd, connfd; struct sockaddr_in servaddr; char buff[4096]; int n; if( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1 ){ printf("create socket error: %s(errno: %d)\n",strerror(errno),errno); exit(0); } memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(listenip); servaddr.sin_port = htons(listenport); if( bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1){ printf("bind socket error: %s(errno: %d)\n",strerror(errno),errno); return -1; } if( listen(listenfd, 10) == -1){ printf("listen socket error: %s(errno: %d)\n",strerror(errno),errno); return -1; } printf("======waiting for client's request======\n"); while(1){ if( (connfd = accept(listenfd, (struct sockaddr*)NULL, NULL)) == -1){ printf("accept socket error: %s(errno: %d)",strerror(errno),errno); continue; } /* create a new thread to handle this connection */ pthread_t tid = 0; int rc = 0; struct thread_arg *parg = malloc(sizeof(struct thread_arg)); if(NULL==parg) { printf("error malloc: %s\n", strerror(errno)); return -1; } parg->socket = connfd; parg->msg_handler = handler; if(0 != (rc=pthread_create(&tid, NULL, thread_f, parg))) { printf("%s: %s\n", __func__, strerror(rc)); } printf(" create thread %u to handle connection %d \n", tid, connfd); } close(listenfd); return 0; } /*************************** * fun: receive one message * params: connfd - socket handle * return: 0 - success; * -1 - error * * **************************/ static int recv_one_message(int connfd, message_handler post_recv_one) { uint32_t msg_len = 0; /* message length */ /* recv length */ if(4 != recvn(connfd, &msg_len, 4)) { // something wrong return -1; } msg_len = ntohl(msg_len); /* recv body */ if(msg_len > 0x7FFFFFFF) { printf("message body to large\n"); return -1; } char* buf = malloc(msg_len);/* allocate memory for message body*/ if(NULL == buf) { printf("%s: malloc failed!\n", __func__); return -1; } if(msg_len != recvn(connfd, buf, msg_len)) { free(buf); return -1; } if(0!=post_recv_one(connfd, buf, msg_len)) { // callback free(buf); return -1; } free(buf); return 0; } /* thread to handle a connection */ static void * thread_f(void * arg) { printf(" enter thread %u\n", pthread_self()); struct thread_arg targ = *((struct thread_arg*)arg); int connfd = targ.socket; message_handler post_recv_one = targ.msg_handler; free(arg); int i = 0; while(1) { if(0 != recv_one_message(connfd, post_recv_one)) { break; } printf("message : %d\n", i++); } close(connfd); printf(" leave thread %u\n", pthread_self()); }
#include "tcputil.h" #include <stdio.h> /* callback called after one message received. */ int msg_handler(int fd, void* buf, uint32_t n) { char* msg = (char*)buf; FILE* fp = fopen("data", "w"); if(NULL == fp) { printf("%s\n", strerror(errno)); fclose(fp); return -1; } if(n != fwrite(msg, 1, n, fp)) { printf("write error:\n"); fclose(fp); return -1; } fclose(fp); return 0; } int main(int argc, char** argv) { start(0,6666, msg_handler); }源码:客户端程序 C#编写
using System; using System.IO; using System.Net; using System.Net.Sockets; namespace ConsoleApplication1 { class Program { static void Main(string[] args) { SendTcpMsg(File.ReadAllBytes(@"F:\核心软件备份\TomatoWin2k3.SP2.R2.iso")); } static void SendTcpMsg(Byte[] msgBody) { Socket sock = null; try { sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); sock.Connect("172.16.35.135", 6666); byte[] msgHead = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(msgBody.Length)); sock.Send(msgHead); sock.Send(msgBody); } catch (Exception ex) { Console.Write(ex.Message); } finally { if(sock!=null) sock.Close(); } } } }