对于这本书没有过实战经验,学起来非常沉闷,搭建服务器应该是用得最多的了,因为我不会用其他的方法搭建
在整个学编程的过程中也是这样,有点点实战就加点血,多看点这样不知道怎么用但是又不得不看不得不知道的东西就掉点血
建立socket,需要调用socket(), bind()和listen()都不能出错。
在书中的案例中使用的是IPV4和TCP创建的socket(IPV6换成AF_INET6, UDP换成SOCK_DGRAM)
self.listenfd = socket(AF_INET, SOCK_STREAM, 0)
然后将socket与一个sockaddr_in structure(servaddr)绑定
bind(self.listenfd, (struct sockaddr *)&servaddr,sizeof(servaddr)
在此之前需要清除servaddr的内存,并对其进行设置
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(port);
port值是给定的,INADDR_ANY的意思是将socket于任意接口绑定
htonl()和htons()是将host byte order 转换为 network byte order,byte order就是计算机读取123的顺序是123还是321
绑定成功后就可以进行监听了,第二个参数设定的是最大鉴定数,这里是1024
listen (self.listenfd, LISTENQ)
完整代码
-(instancetype)initOnPort:(int)port {
self = [super init];
if (self) {
struct sockaddr_in servaddr;
self.errorCode = NOERROR;
if ( (self.listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
self.errorCode = SOCKETERROR;
else {
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(port);
if (bind(self.listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) <0) {
self.errorCode = BINDERROR;
} else {
if ((listen (self.listenfd, LISTENQ)) <0) {
self.errorCode = LISTENERROR;
}
}
}
}
return self;
}
现在我们就有了个在监听port上的新连接的socket,在新连接建立好后改干点有意思的事情
这就是接下来这个方法干的事,监听新连接,并且位置创建一个新线程
-(void)echoServerListenWithDescriptor:(int)lfd {
int connfd;
socklen_t clilen;
struct sockaddr_in cliaddr;
char buf[MAXLINE];
for (;;) {
clilen = sizeof(cliaddr);
if ((connfd = accept(lfd, (struct sockaddr *)&cliaddr, &clilen))<0) {
if (errno != EINTR) {
self.errorCode = ACCEPTINGERROR;
NSLog(@"Error accepting connection");
}
} else {
self.errorCode = NOERROR;
NSString *connStr = [NSString stringWithFormat:@"Connection from %s, port %d", inet_ntop(AF_INET, &cliaddr.sin_addr,buf, sizeof(buf)),ntohs(cliaddr.sin_port)];
NSLog(@"%@", connStr);
//Multi-threaded
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self strEchoServer:@(connfd)];
});
}
}
}
accept()查明并且初始化在监听的socket上的链接,建立一个新连接就产生一个新的socket descriptor(connfd),链接成功输出链接地址和端口
如果不用GCD,服务器一次只能应付一个链接
strEchoServer()对写入文字信息进行监听,并反馈给客户端
-(void)strEchoServer:(NSNumber *) sockfdNum {
ssize_t n;
char buf[MAXLINE];
int sockfd = [sockfdNum intValue];
while ((n=recv(sockfd, buf, MAXLINE -1,0)) > 0) {
[self written:sockfd char:buf size:n];
buf[n]='\0';
NSLog(@"%s",buf);
[[NSNotificationCenter defaultCenter] postNotificationName:
@"posttext" object:[NSString stringWithCString:buf encoding:NSUTF8Str
ingEncoding]];
}
NSLog(@"Closing Socket");
close(sockfdNum);
}
接受信息后再写入,对于客户端来说就是发出信息后再接收相同的信息。
recv()进行接受信息,存储在buf中,n的返回值是0就是没有连接状态,负数是错误,0he负数都会关闭socket,成功的话n的值是接收信息的字节数
-(ssize_t) written:(int)sockfdNum char:(const void *)vptr
size:(size_t)n {
size_t nleft;
ssize_t nwritten;
const char *ptr;
ptr = vptr;
nleft = n;
while (nleft > 0) {
if ( (nwritten = write(sockfdNum, ptr, nleft)) <= 0) {
if (nwritten < 0 && errno == EINTR)
 nwritten = 0;
else
return -1;
nleft -= nwritten;
ptr += nwritten;
}
return(n); }
write()方法与recv类似,ptr是一个指针指向写入的text,每次写入指针都向后移动,nleft就是要写入的字节数,nwritten是返回的已经写入的字节数
可以通过终端调用telnet localhost (port,自己设定的port值,大于1024,小于65535)