运行环境: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);
-
-
- typedef int (*message_handler)(int socket, void * buf, uint32_t size);
-
- int start(uint32_t listenip, uint16_t listenport, message_handler handler);
- #endif
源码:tcputil.c
-
-
-
-
-
-
-
- #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 recvn(int fd, void* buf, size_t n)
- {
- char* ptr = (char*)buf;
- size_t left = n;
- while(left > 0) {
- size_t nread = read(fd, ptr, left);
- if(nread<0) {
- if(errno==EINTR) {
- nread = 0;
- } else {
- return -1;
- }
- } else if(nread==0) {
- break;
- } else {
- left -= nread;
- ptr += nread;
- }
- }
- return (n-left);
- }
-
-
-
-
-
-
-
-
-
- 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 *);
- typedef int (*message_handler)(int, void *, uint32_t);
-
-
-
-
-
-
-
-
- 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;
- }
-
- 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;
- }
-
-
-
-
-
-
-
- static int recv_one_message(int connfd, message_handler post_recv_one)
- {
- uint32_t msg_len = 0;
-
-
- if(4 != recvn(connfd, &msg_len, 4)) {
- return -1;
- }
- msg_len = ntohl(msg_len);
-
-
- if(msg_len > 0x7FFFFFFF) {
- printf("message body to large\n");
- return -1;
- }
- char* buf = malloc(msg_len);
- 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)) {
- free(buf);
- return -1;
- }
- free(buf);
- return 0;
- }
-
- 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());
- }
源码:测试例子 server.c,接收消息写入文件data中。
- #include "tcputil.h"
- #include <stdio.h>
-
-
- 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();
- }
- }
- }
- }