进程间通信:只能是同一台计算机上的不同进程之间通信。
网络编程:实现在网络中的各个计算机之间的通信。
套接字:简单的来讲,套接字就是两个应用程序之间的通信管道的终点,是一种通信机制,凭借这种机制,不同主机之间的进程可以实现通信。
套接字的三个属性:域(domain)、类型(type)、协议(protocol)。
1.套接字的域
域指定套接字通信中使用的网络介质。最常见的套接字域是AF_INET(TCP/IP协议),它是指Internet网络,许多Linux局域网使用的都是该网络,当然,因特网自身用的也是它。
套接字域还有AF_UNIX(UNIX内部使用)、AF_ISO(国际标准组织协议)、AF_NS(xerox网络协议)。
2.套接字类型
流套接字:提供的是一个有序,可靠,双向字节流的连接。由类型SOCK_STREAM指定,通过TCP/IP连接实现。
数据包套接字:它对可以发送的数据包的长度有限制。数据报作为一个单独的网络消息被传输,它可能会丢失,复制或乱序到达。由类型SOCK_DGRAM指定的数据包套接字不建立和维持一个连接。通过UDP/IP连接实现。
3.套接字协议
只要底层的传输机制允许不止一个协议来提供要求的套接字类型,其值默认为0。
创建套接字
socket调用创建一个套接字并返回一个套接字描述符,该描述符可以用来访问套接字。
int socket(int domain,int type,int protocol);
创建的描述符是一条通信线路的一个端点。成功返回一个套接字描述符,失败返回-1,并设置errno。
套接字地址结构:
结构struct sockaddr_un定义了一种通用的套接字地址,类型为:
struct sockaddr_un
{
sa_family_t sun_family; /*AF_UNIX*/
char sun_path; /*pathname*/
};
这是一种通用的定义,一般都不用。
TCP/IP使用的是自己的结构体struct sockaddr_in,格式如下:
struct sockaddr_in
{
short int sin_family; //地址类型,一般为AF_INET
unsigned short int sin_port; //端口号
struct in_addr sin_addr; //IP地址
};
这里的struct in_addr的定义如下:
struct in_addr
{
unsigned long int s_addr;
};
结构体sockaddr和sockaddr_in的长度都是16字节。一般在编TCP/IP程序时,一般使用结构体sockaddr_in来设置地址,然后在需要的时候,通过强制类型转换成sockaddr类型。
函数bind
bind将进程和一个套接字联系起来。bind通常用在服务器进程为接入客户端连接建立一个套接口。
int bind(int sockfd,const struct sockaddr * my_addr,socklen_t addrlen);
参数:sockfd 是socket函数调用返回的套接字。
参数:my_addr是套接字结构的地址。
参数:addrlen设置my_addr能容纳的最大字节数。(一般为16字节)
对于客户端程序来讲,不需要bind,只需要建立连接,而服务端程序还需要调用listen和accept函数。
建立连接:
函数connect用来在一个指定的套接字上创建一个连接,函数原型:
int connect(int sockfd,const struct sockaddr * address,socklen_t addrlen);
客户端调用connect与服务端进行连接。
参数:sockfd是调用socket返回的套接字描述符
参数:address是一个地址结构,指向需要连接的地址
参数:addrlen设置了address能容纳的最大字节数
成功返回0,失败返回-1,并设置errno
服务端在套接字上监听:
函数原型:int listen(int sockfd,int backlog);
参数:sockfd是调用socket返回的套接字值
参数:backlog设置并发接入队列的最大长度,如果已达到最大,则之后的连接请求将被服务器拒绝。
成功返回0,失败返回-1,并设置errno
接收连接:
当客户端连接到服务端后,会排入队列,并用来接收一个连接请求,成功后会返回一个新的套接字描述符,同时原服务端的套接字描述符继续listen指定的端口号。
int accept(int sockfd,struct sockaddr * addr,socklen_t * addrlen);
参数:sockfd是调用socket返回的套接口描述符
参数:addr是指向结构sockaddr的地址,用来保存发起连接请求的主机地址和端口
参数:addrlen设置了addrlen能容纳的最大字节数
函数执行成功返回一个新的代表客户端的套接字,出错则返回-1。并设置errno
客户端与服务端建立连接后,还需要调用recv()和send()函数,就可以在客户端与服务端之间传输数据了。
接收数据:
ssize_t recv(int s,void * buf,size_t len,int flags);
参数:s是已经建立连接的套接字描述符
参数:buf是接收数据内存buffer地址指针。
参数:len知名buffer的大小
参数:flags一般添为0
成功返回接收的字节数,失败返回-1,并设置errno
发送数据:
ssize_t send(int s,void * buf,size_t len,int flags);
socket实例:
客户端程序: client.c
//linux网络编程
//网络编程可以实现网络中各个计算机之间通信,也可同一计算机的进程之间通信
//一个程序使用套接字需要四个步骤
//分配套接口和初始化
//连接
//发送或接收数据
//关闭套接字
//客户端
int main(void)
{
//分配套接口和初始化socket函数,成功返回一个客户端的套接字描述符
int st = socket(AF_INET,SOCK_STREAM,0);//初始化socket
//定义一个套接字地址的结构
struct sockaddr_in addr;
memset(&addr,0,sizeof(addr));
addr.sin_family = AF_INET;//设置结构地址类型为TCP/IP地址
addr.sin_port = htons(8080);//指定一个端口号:8080,htons函数:将short类型从host字节类型到net类型的转化
addr.sin_addr.s_addr = inet_addr("192.168.1.103");//将字符串类型的IP地址转化为int,赋值给addr结构成员
//客户端只需要connect,不需要bind
//调用connect连接到结构addr指定的IP地址和端口号
//即:客户端调用connect与服务器端进行连接
if(connect(st,(struct sockaddr *) &addr,sizeof(addr)) == -1)
{
printf("connect failed %s\n",strerror(errno));
return 0;
}
//与服务器端已经建立了连接,接下来可以实现发送和接收操作了
char buf[1024];
memset(buf,0,sizeof(buf));
while(1)
{
read(STDIN_FILENO,buf,sizeof(buf));//从键盘中读取用户输入
if(send(st,buf,strlen(buf),0) == -1)//发送buf的数据
{
printf("send failed %s\n",strerror(errno));
return 0;
}
}
//发送和接收后,需要断开连接
close(st);//关闭socket
return EXIT_SUCCESS;
}
服务端程序:server.c
//服务器端
//对于服务器端,需要建立自己的套接口等待来自客户端的连接(需要用到listen和accept函数)
int main()
{
int st = socket(AF_INET,SOCK_STREAM,0);
int on = 1;
if(setsockopt(st,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)) == -1)
{
printf("setsockopt failed %s\n",strerror(errno));
return 0;
}
struct sockaddr_in addr;//定义一个套接字地址的结构
memset(&addr,0,sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(8080);//将本地字节顺序转化为网络字节顺序
addr.sin_addr.s_addr = htonl(INADDR_ANY);//INADDR_ANY代表这个server上的所有地址
//服务器端程序需要将IP与server程序绑定
if(bind(st,(struct sockaddr *) &addr,sizeof(addr)) == -1)
{
printf("bind failed %s\n",strerror(errno));
return 0;
}
//服务器端开始监听listen,监听指定端口的客户端连接
if(listen(st,20) == -1)
{
printf("listen failed %s\n", strerror(errno));
return 0;
}
char s[1024];
int client_st = 0;//client端socket
struct sockaddr_in client_addr;//表示client端的IP地址
for(int i = 0;i < 5;i++)
{
memset(&client_addr,0,sizeof(client_addr));
socklen_t len = sizeof(client_addr);
//accept会阻塞,直到有新的客户端连接起来,accept返回一个client的socket描述符
//同时原来的套接口继续监听指定端口号
//若没有客户端与服务器端连接,程序则会一直等待,直到有客户端连接才继续执行
client_st = accept(st,(struct sockaddr *) &client_addr,&len);//accept返回一个client的socket描述符
if(client_st == -1)
{
printf("accept failed %s\n", strerror(errno));
return 0;
}
while(1)
{
memset(s,0,sizeof(1024));
int rc = recv(client_st,s,sizeof(s),0);//recv是阻塞调用,若没有收到消息,则会挂起,此处接收的是来自客户端的套接字描符的数据
if(rc > 0)//接收了来自client的消息
{
printf("recv is %s\n",s);
memset(s,0,sizeof(s));
}
else
{
if(rc == 0)
{
printf("client socket closed\n");
}else
{
printf("recv failed %s\n", strerror(errno));
}
break;
}
}
close(client_st);//关闭client端socket
}
close(st);//关闭server端listen的socket
return 0;
}