Socket编程

这篇文章讲述的是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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值