一、socket介绍
如今社会是万物互联的时代,因此网络成为了最为关键的部分。人能通过人的大脑根据人的外貌特征来准确的区分。那么计算机是怎么识别计算机的呢,这时我们需要给它门一个身份来确定。网络层的“ip地址”能够识别网络中的唯一主机,“端口”能够确定主机中的唯一进程,这样确定了地址信息之后就能进行通信了。如今采用TCP/IP协议大多采用socket进行通信,而且大多部分多着C/S模型,所以学习socket对于嵌入式来说是必不可少的。
二、创建socket服务器的流程
服务器的过程与客户端相差不是很大,有一点不同的是,客户端创建socket时linux内核将自动分配IP地址和端口,服务器创建socket时需要自己设定IP地址和端口,这是时后将调用bind()函数来进设置,然后调用listen监听,当有客户端申请连接时则采用accept进行连接。
三、函数的介绍与运用。
对于大多数函数socket write read close这些已经在这篇博客socket客户端
讲到,现在主要讲讲bind listen accept这些函数。
1、bind
bind(系统调用)函数原型:
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
*将addr中的信息(地址,端口等)分配给sockfd这个套接字。bind函数有如下三个参数:
sockfd:socket所创建的socket描述字,bind将给它addr内的信息。
adddr:一个const struct sockaddr *的指针,指向要绑定sockfd的地址
addlen:addr的长度。
返回值:如果成功,则返回零。出现错误时,将返回-1,并正确设置errno。
示例代码:
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(port);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
rb=bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
首先将servaddr初始化,然后对servaddr进行赋值,servaddr.sin_family = AF_INET;表示地址类型为IPV4(IPV6的话用AF_INET6)。servaddr.sin_port = htons(port);设置绑定的主机端口,注意一定要进行字节转换。servaddr.sin_addr.s_addr = htonl(INADDR_ANY);INADDR_ANY代表着任意的IP地址都能够访问,一般服务器都是这个模型,如果要指定IP连接的话只要对servaddr.sin_addr赋值相应的IP地址即可。
2、listen
listen(系统调用)原型:
int listen(int sockfd, int backlog);
函数功能:listen()将sockfd所指的套接字标记为一个被动套接字,即一个将用于接受一个正在使用connect连接的客户端并转给accept相应。
参数:
sockfd:socket()系统调用创建的要监听的socket描述字
backlog:内核请求连接的socket队列的最大个数。 TCP建立连接是要进行三次握手,但是否完成三次握手后,服务器需要维护这种状态:
半连接状态为:服务器处于Listen状态时收到客户端SYN报文时放入半连接队列中,即SYN queue(服务器端口状态 为:SYN_RCVD)。
全连接状态为:TCP的连接状态从服务器(SYN+ACK)响应客户端后,到客户端的ACK报文到达服务器之前,则一 直保留在半连接状态中;
返回值:如果成功,则返回零。出现错误时,将返回-1,并正确设置errno。
代码示例:
listen(sockfd, 20);
允许最多同时20个客户端进行排队。
3、accept
accept(系统调用)原型:
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
listen激活后,内核会调用accept接受客户端的请求,这个函数默认是一个阻塞函数,一旦客户端调用connect()函数就会触发服务器的accept()返回,这时整个TCP链接就建立好 了。其返回值会返回一个新的socket描述字用来跟客户端相连,然后原来的sockfd继续监听着。
参数:
sockfd:服务器用socket所创建并bind 和listen的socket描述字。
addr:返回客户端的协议地址,有IP地址 端口等信息。
addrlen:返回客户端地址协议的长度。
返回值:成功时,这些系统调用返回一个非负整数,该整数是接受套接字的文件描述符。出错时,-1返回,并正确设置errno。
示例代码
clidfd=accept(sockfd, (struct sockaddr *)&clidaddr, &len);
if(clidfd<0)
{
printf("accept clinet failure:%s\n",strerror(errno));
continue;
}
printf("accept client ip[%s] port[%d]\n",inet_ntoa(clidaddr.sin_addr),ntohs(clidaddr.sin_port));
此时,accept成功后会将客户端的信息保存在addr中,如需获得客户端的IP地址 端口等信息,此时读出addr中的内容即可(注意将网络字节序转换为主机字节序)。
四、源代码
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <getopt.h>
#define MSEG_SER "hello i am server"
int main(int argc ,char **argv)
{
int sockfd = -1;
int on = 1;
struct sockaddr_in servaddr;
struct sockaddr_in clidaddr;
int port = 0;
int rb = -1;
socklen_t len;
int clidfd = -1;
char buf[1024];
port = atoi(argv[1]);
sockfd=socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0)
{
printf("creat sockfd failure:%s\n",strerror(errno));
return -1;
}
printf("creat sockfd sucessful:%d\n",sockfd);
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(port);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
rb=bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
if(rb < 0)
{
printf("socket bind port(%d) failer:%s\n",port,strerror(errno));
return -2;
}
listen(sockfd, 20);
printf("listen to port[%d] sucessful\n",port);
memset(&clidaddr, 0, sizeof(clidaddr));
while(1)
{
printf("waitiing client connect\n");
clidfd=accept(sockfd, (struct sockaddr *)&clidaddr, &len);
if(clidfd<0)
{
printf("accept clinet failure:%s\n",strerror(errno));
continue;
}
printf("accept client ip[%s] port[%d]\n",inet_ntoa(clidaddr.sin_addr),ntohs(clidaddr.sin_port));
if((rb = read(clidfd, buf, sizeof(buf)))<0)
{
printf("read data from clinet failure:%s\n",strerror(errno));
close(clidfd);
continue;
}
else if(rb == 0)
{
printf("no message from clinet\n");
close(clidfd);
continue;
}
else if(rb > 0)
{
printf("get message %d byte from clinet:%s\n",rb,buf);
}
if((rb = write(clidfd, MSEG_SER, strlen(MSEG_SER))) < 0)
{
printf(" write date to clinet failure:%s\n",strerror(errno));
close(clidfd);
continue;
}
printf("write %d byte data to clinet :%s\n",rb,strerror(errno));
close(clidfd);
}
close(sockfd);
}