Socket编程

来源:互联网 发布:dts hd mac 编辑:程序博客网 时间:2024/05/21 05:05

这篇文章讲述的是iOS平台上的BSD Socket编程。最终目的是封装一个向其它终端发送数据的TCP客户端类。

BSD Socket的API这里不作介绍。先封装一个C++类,实现数据的收发,下面是代码实现,关键部分已有注释

.h文件

////  CYSocketClient.h//  TinyPos////  Created by huangcy on 15/4/7.//  Copyright (c) 2015年 huang chaoye. All rights reserved.//#ifndef __TinyPos__CYSocketClient__#define __TinyPos__CYSocketClient__#include <stdio.h>#include <stdlib.h>#include <string.h>#include <arpa/inet.h>#include <sys/types.h>#include <netinet/in.h>#include <sys/socket.h>#include <sys/stat.h>#include <dirent.h>#include <netdb.h>#include <unistd.h>#include <fcntl.h>#include <signal.h>#include <errno.h>#include <netinet/tcp.h>class CYSocketClient {        const int maxSocketBufferSize = 1024;    const int maxErrMessageLen = 200;    private:    char *hostName;    int port;    int sockfd;    struct sockaddr_in sa;    bool isConnected;    char *errMessage;        void setErrMessage(const char *errorMessage);    void setNonBlock(int fd, bool nonBlock);    public:        CYSocketClient(const char *hostName, const int port);    ~CYSocketClient();        const char *getErrorMessage();        bool connectToServer(int timeoutSec = 30);    bool sendDataToServer(const char *sendBuffer, int dataLen);    ///从服务端读取数据到recvBuffer,recvBuffer的内存需要手动释放    ssize_t recvDataFromServer(char *&recvBuffer);    void disConnectToServer();        bool getConnectState();};#endif /* defined(__TinyPos__CYSocketClient__) */

.cpp文件

////  CYSocketClient.cpp//  TinyPos////  Created by huangcy on 15/4/7.//  Copyright (c) 2015年 huang chaoye. All rights reserved.//#include "CYSocketClient.h"CYSocketClient::CYSocketClient(const char *hostName, const int port) {        size_t len = strlen(hostName) + 1;    this->hostName = new char[len];    memset(this->hostName, 0, len);    strncpy(this->hostName, hostName, len - 1);    this->port = port;        sockfd = -1;    isConnected = false;        errMessage = new char[maxErrMessageLen+1];    memset(errMessage, 0, maxErrMessageLen+1);}CYSocketClient::~CYSocketClient() {        delete hostName;    delete [] errMessage;}const char * CYSocketClient::getErrorMessage() {        return errMessage;}void CYSocketClient::setErrMessage(const char *errorMessage) {        memset(this->errMessage, 0, maxErrMessageLen);    strncpy(this->errMessage, errorMessage, maxErrMessageLen);}void CYSocketClient::setNonBlock(int fd, bool nonBlock) {        int flags = fcntl(fd, F_GETFL, 0);    if (nonBlock) {        fcntl(fd, F_SETFL, flags | O_NONBLOCK);    } else {        flags &= ~ O_NONBLOCK;        fcntl(fd, F_SETFL, flags);    }}bool CYSocketClient::connectToServer(int timeoutSec) {        struct hostent *hptr;    hptr = gethostbyname(hostName);    if (hptr == nullptr) {        setErrMessage("gethostbyname return null");        return false;    }        bcopy((char *)hptr->h_addr, (char *)&sa.sin_addr, hptr->h_length);    sa.sin_family = hptr->h_addrtype;    sa.sin_port = htons(port);        sockfd = socket(hptr->h_addrtype, SOCK_STREAM, 0);    if (sockfd < 0) {        setErrMessage("create socket failed");        return false;    }        if (setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &maxSocketBufferSize, sizeof(maxSocketBufferSize)) < 0) {        setErrMessage("setsockopt (SO_SNDBUF) failed");        return false;    }        if (setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &maxSocketBufferSize, sizeof(maxSocketBufferSize)) < 0) {        setErrMessage("setsockopt (SO_RCVBUF) failed");        return false;    }        struct timeval tv;    tv.tv_sec = timeoutSec;    tv.tv_usec = 0;        if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) {        setErrMessage("setsockopt (SO_RCVTIMEO) failed");        return false;    }        if (setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) < 0) {        setErrMessage("setsockopt (SO_SNDTIMEO) failed");        return false;    }        setNonBlock(sockfd, true);        bool result = true;        if (connect(sockfd, (struct sockaddr *)&sa, sizeof(sa)) == 0) {                setNonBlock(sockfd, false);            } else {        //以非阻塞的方式来进行连接的时候,返回的结果如果是 -1,这并不代表这次连接发生了错误        //可以通过select来判断socket是否可写,如果可以写,说明连接完成了,否则判断是否超时或存在错误        fd_set wset;        FD_ZERO(&wset);        FD_SET(sockfd, &wset);                int ret = select(sockfd + 1, nullptr, &wset, nullptr, &tv);        if (ret == 0) {            setErrMessage("timeout");            result = false;        } else {            int error = 0;            int errlen = sizeof(error);            getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, (socklen_t *)&errlen);            if (error != 0){                setErrMessage(strerror(errno));                result = false;            } else {                setNonBlock(sockfd, false);                int set = 1;                //避免服务端连接关闭后,继续向服务端发数据出现SIGPIPE错误                setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&set, sizeof(int));            }        }    }        if (result == true) {        isConnected = true;    }        return result;}bool CYSocketClient::sendDataToServer(const char *sendBuffer, int dataLen) {        if (!isConnected) {                setErrMessage("invalid socket connect");        return false;    }        const int maxRetryTimes = 3;    int retryTimes = 0;    int bytesWrite = 0;        while (dataLen - bytesWrite > 0) {                ssize_t writeLen = send(sockfd, sendBuffer + bytesWrite, dataLen - bytesWrite, 0);        if (writeLen <= 0) {                        setErrMessage(strerror(errno));                        if (errno == EINTR) {//EINTR是由于信号中断导致失败,可继续重试                if (++retryTimes >= maxRetryTimes) {                    break;                }                continue;            }            else if (errno == EAGAIN) {                if (++retryTimes >= maxRetryTimes) {                    break;                }                continue;            }            else {                break;            }        }                bytesWrite += writeLen;    }        if (bytesWrite != dataLen) {        return false;    }    return true;}ssize_t CYSocketClient::recvDataFromServer(char *&recvBuffer) {        if (!isConnected) {                setErrMessage("invalid socket connect");        return false;    }        int bufferSize = maxSocketBufferSize + 1;    if (recvBuffer != nullptr) {        delete [] recvBuffer;    }    recvBuffer = new char[bufferSize];        ssize_t totalRecvBytes = 0;    ssize_t currentRecvBytes = 0;    int maxRetryTimes = 3;    int retryTimes = 0;    while(1) {                currentRecvBytes = recv(sockfd, recvBuffer + totalRecvBytes, maxSocketBufferSize, 0);        if (currentRecvBytes < 0) {                        setErrMessage(strerror(errno));                        if (errno == EINTR) {                if (++retryTimes < maxRetryTimes) {                    continue;                } else {                    isConnected = false;//连接已断开                    break;                }            } else if (errno == EAGAIN) {//期望读取数据时,没有数据可读,可能时服务端还没有准备好,可重试                if (++retryTimes < maxRetryTimes) {                    continue;                } else {                    isConnected = false;//连接已断开                    break;                }            }        } else if (currentRecvBytes == 0) {                        isConnected = false;//连接已断开            break;        }                totalRecvBytes += currentRecvBytes;        if (totalRecvBytes >= bufferSize) {            bufferSize += maxSocketBufferSize;            char *newBuffer = new char[bufferSize];            bcopy(recvBuffer, newBuffer, totalRecvBytes);            delete [] recvBuffer;            recvBuffer = newBuffer;        }                if (currentRecvBytes < maxSocketBufferSize) {//已接收完成            break;        }    }        if (!isConnected) {        setErrMessage("socket has disconnect");    }        recvBuffer[totalRecvBytes] = 0x0;    return totalRecvBytes;}void CYSocketClient::disConnectToServer() {    close(sockfd);    isConnected = false;}bool CYSocketClient::getConnectState() {        return isConnected;}


以上代码是对BSD Socket C API的C++封装,这样做的目的是避免在OC的代码中直接调用C代码。

相应地,在OC中直接调用C++的代码恐怕也不太好,因为有些地方是需要手动释放内存的,例如上面所封装的类。

所以,这里使用OC对上面的类进行封装。

.h文件

////  TCPClient.h//  TinyPos////  Created by huangcy on 15/4/7.//  Copyright (c) 2015年 huang chaoye. All rights reserved.//#import <Foundation/Foundation.h>@interface TCPClient : NSObject@property (readonly, strong, nonatomic) NSString *hostName;@property (readonly, nonatomic) NSInteger port;- (instancetype)initWithHostName:(NSString *)hostName port:(NSInteger)port;- (BOOL)connectWithTimeoutSec:(int)timeoutSec errMessage:(NSMutableString *)errMessage;- (BOOL)send:(NSString *)message errMessage:(NSMutableString *)errMessage;- (NSInteger)recv:(NSMutableData *)data errMessage:(NSMutableString *)errMessage;- (BOOL)getConectState;- (void)close;@end
.mm文件

////  TCPClient.m//  TinyPos////  Created by huangcy on 15/4/7.//  Copyright (c) 2015年 huang chaoye. All rights reserved.//#import "TCPClient.h"#import "CYSocketClient.h"@interface TCPClient (){    CYSocketClient *_socketClient;}@end@implementation TCPClient- (instancetype)initWithHostName:(NSString *)hostName port: (NSInteger)port {        self = [super init];        _hostName = hostName;    _port = port;        _socketClient = new CYSocketClient([hostName UTF8String], int(_port));        return self;}- (void)dealloc {        delete _socketClient;}- (BOOL)connectWithTimeoutSec:(int)timeoutSec errMessage:(NSMutableString *)errMessage {        BOOL result = _socketClient->connectToServer(timeoutSec);    if (result == NO) {        [errMessage setString:[NSString stringWithUTF8String:_socketClient->getErrorMessage()]];    }        return result;}- (BOOL)send:(NSString *)message errMessage:(NSMutableString *)errMessage {        NSData *data = [message dataUsingEncoding:NSUTF8StringEncoding];    BOOL result = _socketClient->sendDataToServer((const char *)data.bytes, (int)data.length);    if (!result) {        [errMessage setString: [NSString stringWithUTF8String:_socketClient->getErrorMessage()]];    }    return result;}- (NSInteger)recv:(NSMutableData *)data errMessage:(NSMutableString *)errMessage {        char *recvBuffer = nullptr;    NSInteger recvCount = _socketClient->recvDataFromServer(recvBuffer);    if (recvCount > 0) {        [data setData:[NSData dataWithBytes:recvBuffer length:recvCount]];    } else {        [errMessage setString: [NSString stringWithUTF8String:_socketClient->getErrorMessage()]];    }    delete [] recvBuffer;        return recvCount;}- (BOOL)getConectState {        return _socketClient->getConnectState();}- (void)close {        _socketClient->disConnectToServer();}@end

以上的代码基本实现了客户端的初始化、连接和发送接收数据。

下面写个例子演示以下使用方法。

先写个服务端,用C#写吧,没有比这个更简单了,测试服务端的代码:

using System;using System.Net;using System.Net.Sockets;using System.Text;using System.Threading;namespace tcpserver{    /// <summary>    /// Class1 的摘要说明。    /// </summary>    class server    {        /// <summary>        /// 应用程序的主入口点。        /// </summary>        [STAThread]        static void Main(string[] args)        {            byte[] data = new byte[1024];            IPEndPoint ipep = new IPEndPoint(IPAddress.Any, 19600);            Socket newsock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);            newsock.Bind(ipep);            newsock.Listen(10);            while (true)            {                Console.WriteLine("等待客户端连接...");                Socket client = newsock.Accept();                IPEndPoint clientip = (IPEndPoint)client.RemoteEndPoint;                Console.WriteLine("客户端已连接: " + clientip.Address + ":" + clientip.Port);                Thread thread = new Thread(new ParameterizedThreadStart(test));                thread.Start(client);            }        }        public static void test(object c)        {            Socket client = c as Socket;            int recv;            byte[] data = new byte[1024];            while (true)            {                data = new byte[1024];                recv = client.Receive(data);                IPEndPoint clientip = (IPEndPoint)client.RemoteEndPoint;                Console.WriteLine("收到({0})发来的数据{1}", clientip, recv);                if (recv == 0)//客户端连接断开                    break;                string str = Encoding.ASCII.GetString(data, 0, recv);                if (str.Contains("[CLOSE]"))                {                    break;                }                client.Send(data, recv, SocketFlags.None);            }            client.Shutdown(SocketShutdown.Both);            client.Close();        }    }}
简单解释一下,上面的代码主要的功能是在Main方法中等待客户端连接,检测到客户连接后开辟新线程处理客户端发过来的数据。

即把客户端发来的数据回传给客户端,收到[CLOSE]消息时关闭客户连接。

下面是OC端测试代码:

for (NSInteger i = 0; i <10; i++) {                dispatch_async(dispatch_get_global_queue(0, 0), ^{                        TCPClient *client = [[TCPClient alloc] initWithHostName:@"192.168.1.2" port:19600];            NSMutableString *errMessage = [[NSMutableString alloc] init];                        BOOL success = [client connectWithTimeoutSec:15 errMessage:errMessage];            if (!success) {                                NSLog(@"第%ld次连接失败了 %@", i + 1, errMessage);                            } else {                                success = [client send:[NSString stringWithFormat:@"第%ld次", i + 1] errMessage:errMessage];                if (!success) {                    NSLog(@"第%ld次发送失败了 %@", i + 1, errMessage);                }                                NSMutableData *recvData = [[NSMutableData alloc] init];                success = [client recv:recvData errMessage:errMessage];                if (!success) {                    NSLog(@"第%ld次接收失败了 %@", i + 1, errMessage);                } else {                    NSString *recvStr = [[NSString alloc] initWithData:recvData encoding:NSUTF8StringEncoding];                    NSLog(@"第%ld次接收到数据: %@", i + 1, recvStr);                }                                if (i == 5) {                                        success = [client send:@"[CLOSE]" errMessage:errMessage];                    if (!success) {                        NSLog(@"发送关闭消息失败 %@", errMessage);                    }                                        NSMutableData *recvData = [[NSMutableData alloc] init];                    success = [client recv:recvData errMessage:errMessage];                    if (!success) {                        NSLog(@"发送关闭消息后读数据监测到的错误 %@", errMessage);                    } else {                        NSString *recvStr = [[NSString alloc] initWithData:recvData encoding:NSUTF8StringEncoding];                        NSLog(@"发送关闭消息后收到的数据: %@", recvStr);                    }                                        success = [client send:@"Hello World!" errMessage:errMessage];                    if (!success) {                        NSLog(@"再次发送失败 %@", errMessage);                    }                }                if (i == 6) {                                        sleep(5);//延时5秒再发数据,期间关闭服务端,观察此时发送数据的情况                    success = [client send:[NSString stringWithFormat:@"第%ld次", i + 1] errMessage:errMessage];                    if (!success) {                        NSLog(@"关闭服务端后发数据时监测到的错误 %@", errMessage);                    }                }            }                        [client close];        });    }
下面是输出的结果:


0 0
原创粉丝点击