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
- socket编程--socket基本概念
- socket编程--socket基本概念
- socket编程
- socket编程
- Socket 编程
- socket编程
- Socket编程
- Socket编程
- Socket编程
- Socket编程
- SOCKET编程
- socket编程
- Socket编程
- socket编程
- Socket 编程
- Socket 编程
- socket 编程
- socket编程
- Xpdf使用说明之pdftotext
- 高精度问题分析
- mybatis整合redis重写 生成key的方法
- 古堡算式
- java加密与签名
- Socket编程
- 企业管理的笑话,暗藏真理
- STM32_定时器_PWM_笔记
- IDEA Error:java: 未结束的字符串文字
- #define,const ,static 的使用总结
- ch6.正则表达式
- jqwidgets之jqxGrid后台分页排序过滤
- AppCompatActivity的出现
- Java自动内存管理偏爱二