Linux下socket网络编程
简介
这里打算写一个在Linux系统下运行的socket服务端,然后在win下使用网络助手与其连接,达到数据回显的功能。废话不多说了,咱们现在开始。
Linux下socket网络服务端的固定套路
- 创建socket。
- 将创建socket时返回的套接字描述符与服务器IP地址和端口进行绑定。
- 开始监听连接到该服务器的客户端。
- 当有客户端请求与该服务端程序进行连接时,接受客户端的连接
- 进行数据传输
- 关闭连接
下面逐步对上述6个步骤使用到的函数进行分析
1、创建socket
int socket( int domain, int type,int protocol)
domain:
系统使用的底层协议族,参数一般为AF_INET
(IPv4使用)或者AF_INET6
(IPv6使用),当然还有其他类型的参数,这里就不一一介绍了。一般我们使用AF_INET
就可以。
type:
指定使用的套接字类型,SOCK_STREAM
字节流套接字,我们编写TCP程序时可以使用该参数。SOCK_DGRAM
用户数据报文协议,使用UDP传输时,我们可以指定为该参数。
protocol:
使用的协议,一般该参数直接指定为 0 就好。
2、绑定
int bind(int sockfd, struct sockaddr *myaddr,int addrlen)
socket:
第一步创建socket时返回的套接字描述符。
myaddr:
指向绑定的服务器IP地址的结构体指针。
addrlen:
服务器IP地址的结构体的长度。
3、监听
int listen(int sockfd,int backlog)
sockfd:
第一步创建socket时返回的套接字描述符。
backlog:
设置可连接客户端的最大连接个数,当多个客户端在同一时刻连接该服务时,能够允许的个数受到该值得影响。
4、接受客户端连接请求
int accept(int sockfd,struct sockaddr *cliaddr,socklen_t *addrlen)
sockfd:
第一步创建socket时返回的套接字描述符。
cliaddr:
指向请求连接到该服务器端的客户端的IP地址和端口号
addrlen:
指向客户端IP地址和端口号的结构体长度的指针。
5、数据传输
int send(int sockfd, const void *msg,int len,int flags)
int recv(int sockfd, void *buf,int len,unsigned int flags)
sockfd:
第一步创建socket时返回的套接字描述符。
msg:
发送数据的指针
buf:
存放接收数据的缓冲区
len:
数据的长度,把flags设置为0
6、关闭连接
int close(int fd)
fd:
待关闭的的socket
小提示
之所以要同时指定IP地址和端口号,咱们可以这样理解。比如我想使用微信给你发送一条消息,那么我们使用的电脑就必须知道你使用电脑的IP地址。但是仅仅知道IP地址并不能完成上述的要求,因为你的电脑上运行了很多的程序,计算机需要知道要把这个消息发送到你电脑上的哪一个软件,这时就需要用到端口号了。
服务端代码
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/un.h>
#define MAX_CLIENT_NUM 5 //最大监听客户端连接数量为5
int main(int argc,char *argv[])
{
char buf[512];
int sfd;
if(argc != 3)
{
printf("please input PORT and IP ADDR!\n");
return 0;
}
sfd = socket(AF_INET,SOCK_STREAM,0);
if (sfd == -1)
printf("socker create faild!\n");
struct sockaddr_in serveraddr;
bzero(&serveraddr,sizeof(struct sockaddr_in));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(atoi(argv[2])); //服务器监听端口号
serveraddr.sin_addr.s_addr = inet_addr(argv[1]); //此处IP地址必须为linux下网口ip地址
if(bind(sfd,(struct sockaddr*)&serveraddr,sizeof(struct sockaddr)) == -1)
{
printf("bind error!\n");
close(sfd);
}
if(listen(sfd,MAX_CLIENT_NUM) == -1)
{
printf("listen!\n");
close(sfd);
}
int new_fd;
struct sockaddr_in client_addr;
int socket_len = sizeof(struct sockaddr_in);
bzero(&client_addr,socket_len);
new_fd = accept(sfd,(struct sockaddr*)&client_addr,&socket_len);
if(new_fd == -1)
{
printf("--accept error-----------");
return 0;
}
else //打印请求连接的客户端IP地址和端口号
{
char *ptr = inet_ntoa(client_addr.sin_addr); //IP地址,inet_ntoa函数将32位无符号的IP地址转化为X.X.X.X的形式
unsigned short port_tmp = ntohs(client_addr.sin_port);
printf("------------------------------------------------------\n");
printf("client ip :%s---port:%d\n",ptr,port_tmp);
printf("------------------------------------------------------\n");
}
while(1)
{
//数据回显功能
int rev_num = recv(new_fd,buf,sizeof(buf),0);
//客户端主动关闭网络连接
if(rev_num == 0)
{
close(new_fd);
break;
}
send(new_fd,buf,rev_num,0);
}
printf("client close!\n");
close(sfd);
return 1;
}
代码测试
将编译好的代码放到Linux下进行编译,并通过ifconfig查看Linux下网络的IP地址,使用ipconfig查看win下IP地址。确保其能够相互ping通。
运行编译后的程序------记得同时指定IP地址和端口号
总结
在介绍int listen(int sockfd,int backlog)
函数时,我们说过其第二个参数是控制多个客户端同时请求连接时的数量的,但是在以上的代码中。我们并没有充分的使用到这个功能,那是因为TCP连接服务端程序的第4步接受客户端连接请求所使用的函数int accept(int sockfd,struct sockaddr *cliaddr,socklen_t *addrlen)
默认是阻塞的。我们想要实现回显功能,就不能将其放置到 while(1)
的循环中,但是放置到外面就会出现在一个时刻只能连接一个客户端。为了解决这个问题现行的方法有多进程、多线程、poll
和select
处理,下一章将用select
函数实现网络编程的IO并发处理。