socket也叫“套接字”,虽然但是吧这个翻译其实很不好,不利于初学者理解,socket可以理解成计算机提供给程序员的接口,数据在客户端和服务端之间的socket之间传输。socket把复杂的TCP/IP协议封装,对于程序员来说只要利用好函数,就可以实现数据通信。
TCP提供了stream和datagram两种通信机制,所以socket分这两种。
stream的类型是SOCK_STREAM,采用TCP协议,TCP协议在计算机网络中是安全可靠的有连接的协议。datagram的类型是SOCK_DGRAM,采用的是UDP协议,UDP是不可靠的协议,现在在实际应用开发中,主要采用的是TCP。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
int listenfd;
listenfd=socket(AF_INET,SOCK_STREAM,0);//在socket编程中,AF_INET是必须的,等同于固定搭配
//socket创建成功后如果返回值是-1的话,说明创建失败,为0的时候就是创建成功
if(listenfd==-1)
{
printf("socket create fail\n");
return -1;
}
struct sockaddr_in serveraddr;//定义一个用来处理网络通信的数据结构,sockaddr_in分别将端口和地址存储在两个结构体中
//sin_family协议族
memset(&serveraddr,0,sizeof(serveraddr));
serveraddr.sin_family=AF_INET;
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
//serveraddr.sin_addr.s_addr=atoi(argv[1]);// specify ip address
serveraddr.sin_port=htons(atoi(argv[1]));//specify port
//printf("%s %s\n",argv[1],argv[2]);
if(bind(listenfd,(struct sockaddr *)&serveraddr,sizeof(serveraddr))!=0)
{
printf("bind failed \n");
return -1;
}
argv[1]是运行可执行文件时,后面提供的参数。
使得一个进程可以接受其它进程的请求,从而成为一个服务器进程。在TCP服务器编程中listen函数把进程变为一个服务器,并指定相应的套接字变为被动连接。
if(listen(listenfd,5)!=0)
{
printf("Listen failed\n");
close(listenfd);
return -1;
}
函数声明
int listen(int sockfd, int backlog);//返回0=成功 -1则失败
参数sockfd是已经被bind过的socket。socket函数返回的socket是一个主动连接的socket,在服务端的编程中,程序员希望这个socket可以接受外来的连接请求,也就是被动等待客户端来连接。由于系统默认时认为一个socket是主动连接的,所以需要通过某种方式来告诉系统,程序员通过调用listen函数来完成这件事。
参数backlog,这个参数涉及到一些网络的细节,比较麻烦,填5、10都行,一般不超过30。当调用listen之后,服务端的socket就可以调用accept来接受客户端的连接请求。
服务端接受客户端的请求
int clintfd;//socket for client
int socklen=sizeof(struct sockaddr_in);
struct sockaddr_in client_addr;
clintfd=accept(listenfd,(struct sockaddr*)&client_addr,(socklen_t *)&socklen);
if(clintfd==-1)
printf("connect failed\n");
else
printf("client %s has connnected\n",inet_ntoa(client_addr.sin_addr));
其中accept函数如下
int iret;
memset(buf,0,sizeof(buf));
iret=recv(clintfd,buf,strlen(buf),0);
if(iret<=0)
{
perror("send");
break;
}
printf("receive %s\n",buf);
recv函数用于server端socket传送过来的数据,函数声明如下:
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
这时候这里的sockfd其实是clientfd,buf中存储接受的数据,如果client的socket没有发送数据,recv函数就会一直等待,如果发送了数据,函数返回接受到的字符数,如果socket关闭那么就会返回0
char buffer[1024];
while (1)
{
int iret;
memset(buffer,0,sizeof(buffer));
iret=recv(clintfd,buffer,sizeof(buffer),0);
if (iret<=0)
{
printf("iret=%d\n",iret); break;
}
printf("receive :%s\n",buffer);
strcpy(buffer,"ok");//reply cilent with "ok"
if ( (iret=send(clintfd,buffer,strlen(buffer),0))<=0)
{
perror("send");
break;
}
printf("send :%s\n",buffer);
}
send函数用于把数据通过socket发送给对端。不论是客户端还是服务端,应用程序都用send函数来向TCP连接的另一端发送数据。
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
返回值是0的时候,通信已经中断。
close(listenfd);
close(clintfd);
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
int main(int argc,char *argv[])
{
// first step ->create socket for server
int listenfd;
listenfd=socket(AF_INET,SOCK_STREAM,0);// in socket code,it must be AF_INET(protocol)
if(listenfd==-1)
{
printf("socket create fail\n");
return -1;
}
//second step bind ->server's ip&port for communication to socket created in fist step
struct sockaddr_in serveraddr;
memset(&serveraddr,0,sizeof(serveraddr));
serveraddr.sin_family=AF_INET;
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
//serveraddr.sin_addr.s_addr=atoi(argv[1]);// specify ip address
serveraddr.sin_port=htons(atoi(argv[1]));//specify port
//printf("%s %s\n",argv[1],argv[2]);
if(bind(listenfd,(struct sockaddr *)&serveraddr,sizeof(serveraddr))!=0)
{
printf("bind failed \n");
return -1;
}
//Third step ->Set socket to listening mode
/*
The listen function changes the active connection socket interface into the connected socket interface,
so that a process can accept the requests of other processes and become a server process.
In TCP server programming, the listen function changes the process into a server and specifies that the corresponding socket becomes a passive connection.
*/
if(listen(listenfd,5)!=0)
{
printf("Listen failed\n");
close(listenfd);
return -1;
}
// 4th step -> receive client's request
int clintfd;//socket for client
int socklen=sizeof(struct sockaddr_in);
struct sockaddr_in client_addr;
clintfd=accept(listenfd,(struct sockaddr*)&client_addr,(socklen_t *)&socklen);
if(clintfd==-1)
printf("connect failed\n");
else
printf("client %s has connnected\n",inet_ntoa(client_addr.sin_addr));
// 5th step ->connect with client,receive data and reply OK
char buffer[1024];
while (1)
{
int iret;
memset(buffer,0,sizeof(buffer));
iret=recv(clintfd,buffer,sizeof(buffer),0);
if (iret<=0)
{
printf("iret=%d\n",iret); break;
}
printf("receive :%s\n",buffer);
strcpy(buffer,"ok");//reply cilent with "ok"
if ( (iret=send(clintfd,buffer,strlen(buffer),0))<=0)
{
perror("send");
break;
}
printf("send :%s\n",buffer);
}
// 6th close socket
close(listenfd); close(clintfd);
}
所需头文件如下
#include
#include
#include
#include
#include
#include
int sockfd;
sockfd = socket(AF_INET,SOCK_STREAM,0))//同服务端操作相同
//way 1
struct hostent* h;
if ( (h = gethostbyname(argv[1])) == 0 ) // 指定服务端的ip地址。
{
printf("gethostbyname failed.\n");
close(sockfd);
return -1;
}
struct sockaddr_in servaddr;
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(atoi(argv[2])); // 指定服务端的通信端口。
memcpy(&servaddr.sin_addr,h->h_addr,h->h_length);
if (connect(sockfd, (struct sockaddr *)&servaddr,sizeof(servaddr)) != 0)
// 向服务端发起连接清求。
{
perror("connect");
close(sockfd);
return -1;
}
// way 2
struct sockaddr_in servaddr;
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(atoi(argv[2])); // server's port
servaddr.sin_addr.s_addr=inet_addr(argv[1]);//server's ip
if (connect(sockfd, (struct sockaddr *)&servaddr,sizeof(servaddr)) != 0) // send request to server for connection
{
perror("connect");
close(sockfd);
return -1;
}
其中用到的结构体和函数介绍如下
struct hostent//
struct hostent
{
char *h_name; //正式主机名
char **h_aliases; //主机别名
int h_addrtype; //主机IP地址类型:IPV4-AF_INET
int h_length; //主机IP地址字节长度,对于IPv4是四字节,即32位
char **h_addr_list; //主机的IP地址列表
};
#define h_addr h_addr_list[0] //保存的是IP地址
这里面使用的gethostbyname函数,只能用在客户端上,主要作用就是把字符串的IP地址转换成结构体的ip地址。
while(1){
char buffer[1024];
int iret;
int choice;
printf("if you want to continue to chat please input 1,or input 2\n");
scanf("%d\n",&choice);
if(choice==2)
break;
memset(buffer,0,sizeof(buffer));
printf("please input what you want to say\n");
scanf("%s",buffer);
if ( (iret=send(sockfd,buffer,strlen(buffer),0))<=0) // 向服务端发送请求报文。
{
perror("send");
break;
}
printf("send:%s\n",buffer);
memset(buffer,0,sizeof(buffer));
if ( (iret=recv(sockfd,buffer,sizeof(buffer),0))<=0) // 接收服务端的回应报文。
{
printf("iret=%d\n",iret);
break;
}
printf("receive:%s\n",buffer);
}
}
close(sockfd);
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
int main(int argc,char *argv[])
{
// first step create socket
int sockfd;
if ( (sockfd = socket(AF_INET,SOCK_STREAM,0))==-1)
{
perror("socket");
return -1;
}
// second step: send request to server for connection
struct sockaddr_in servaddr;
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(atoi(argv[2])); // server's port
servaddr.sin_addr.s_addr=inet_addr(argv[1]);//server's ip
if (connect(sockfd, (struct sockaddr *)&servaddr,sizeof(servaddr)) != 0) // send request to server for connection
{
perror("connect");
close(sockfd);
return -1;
}
char buffer[1024];
// 3th step connect with server
while(1)
{
int iret;
memset(buffer,0,sizeof(buffer));
int choice;
printf("if you want to continue to chat please input 1 or input else\n");
scanf("%d",&choice);
getchar();
if(choice!=1)
break;
else
{
printf("please input what you want to say\n");
cin.getline(buffer,1024);
}
if ( (iret=send(sockfd,buffer,strlen(buffer),0))<=0) // send data to server
{
perror("send");
break;
}
printf("send: %s\n",buffer);
memset(buffer,0,sizeof(buffer));
if ( (iret=recv(sockfd,buffer,sizeof(buffer),0))<=0) // receive server's reply
{
printf("iret=%d\n",iret);
break;
}
printf("receive: %s\n",buffer);
}
close(sockfd);
}
TCP服务器端依次调用socket()、 bind()、listen()之后,就会监听指定的socket地址了。TCP客户端依次调用socket()、connect()之后就想TCP服务器发送了一个连接请求。TCP服务器监听到这个请求之后,就会调用accept()函数取接收请求,这样连接就建立好了。之后就可以开始网络I/O操作了,即类同于普通文件的读写I/O操作。
对于服务端来说有两个socket,这里该如何理解呢?
首先在linux内核中,socket函数创建的都是主动套接字,但是服务端在经过listen()以后呢,会转换成被动socket,经过accept函数处理后又会变成已连接的socket,在成为已连接socket之前,还是同一个socket,但是这里面状态发生了改变,服务端经过accept之后的socket是新的socket,用于连接之后的读写操作。
监听socket,是服务器作为客户端连接请求的一个对端,只需创建一次能够让客户端请求到有这个端点就ok,所以监听socket(listen_socket_fd)存在于服务器的整个生命周期, 不需要每个连接都创建一个新的监听socket出来, 没必要呢。已连接socket(connect_socket_fd)是客户端与服务器之间已经建立起来了的连接的一个端点,服务器每次接受连接请求时都会创建一个新已连接socket,它的生命周期只是客户端请求服务端的时间范围内。