一,IP地址,端口
我们进行网络通信时,需要的最基本的信息就是客户端的IP地址,端口,以及服务端的IP地址,端口,就比如当你需要邮寄一个快递时,你需要知道你现在在哪里,以及快递的目的地是哪里,同理,你想让一台计算机给另外一台计算机发消息,那么就需要知道自己的IP地址和端口号,以及对方的IP地址和端口号。
(1)IP地址;现阶段你可以理解为,计算机通过不同的ip地址来区别不同的主机,所以你在通信时需要知道目标主机和当前主机的ip地址才能够让两个计算机实现通信。
(2)端口;我们的计算机里面运行着不止一个应用,那么我们如何区分某个信息是哪一个应用发出的,想让目标主机的哪一个应用接收呢?,没错,这时候便需要用到端口了,你可以理解为每个应用通信时系统都会为其分配一个端口号,这个端口号是独一无二的,所以你就能识别是那个软件发出的信息,需要那个对应的软件来接收了。
二,简单的socket通信的过程,流程。
根据流程图我们先大体了解一下过程,这些函数的具体使用方法我们后面会讲到。
三,相关函数调用了解(五个基本函数)
1,socket()调用
socket()你可以当成creat()去理解,只不过creat()是创建了一个普通的文件类型,而socket()是创建了一个socket套接字类型的文件用于通信,socket()的返回值是一个文件描述符,而他的参数,决定了你会使用什么样的ip协议去通信(比如,用ipv4还是ipv6来通信,用其中哪个的协议族的哪一个去通信,具体到哪一个协议),这正好对应了他的三个参数,分别是协议族,socket类型,以及指定的协议。下面是用法和参数。
int socket(int domain, int type, int protocol)
domain:协议族。例如AF_INET、AF_INET6即(ipv4,ipv6)等。
type:指定socket类型。常用的socket类型有,SOCK_STREAM、SOCK_DGRAM等。
protocol:最终确定的协议,设为0时,会自动选择type类型对应的默认协议。
2,bind()调用
前面,我们socket()指定了我们会使用什么样的协议去通信,但是我们对应的ip地址和端口都还是空着的,我们bind()的作用便是给socket()绑定上确定的协议头,IP地址以及端口,当然,在绑定之前,我们需要将对应的数据初始化。(可以对照后面的示例代码去理解)。下面是用法和参数。
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd:socket的文件描述符。
个名字。
addr:一个指针,指向要给sockfd的协议地址。
addrlen:地址的长度。
3,listen()调用
socket()默认是一个主动类型的套接字类型,而我们服务端进行通信是不是需要“监听”这个端口呢?没错,这个函数就是将socket()变成被动监听类型的socket(),指定他要监听的IP地址以及端口。下面是用法和参数。
int listen(int sockfd, int backlog);
sockefd: 要监听的socket对应的文件描述符。
backlog: 最多可以等待几个客户端,比如有设置为5,那么连接第6个客户端连接服务器时,就连接不进来了
4,accept()调用
服务端监听对应的IP和端口,但是我们如何知道客户端何时连接了服务端呢?这个时候我们可以通过accept()函数来实现。下面是用法和参数。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
sockfd: listen()后的被动的socket描述字;
*addr: 用于存储客户端的ip地址以及端口的一个指针;
addrlen: 用来存储客户端协议地址的长度的指针;
5,connect()调用
客户端和服务端通信,客户端需要向服务端发起连接的请求,服务端收到请求后和客户端连接,那么服务端响应客户端的连接使用的是accept()函数,那么客户端需要依靠什么函数来发送请求呢?没错,就是connect()函数。下面是用法和参数。
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd: 客户端的socket对应的文件描述符。
addr: 需要连接的服务器的socket信息,含有服务器的IP地址和端口等。
addrlen: 服务器地址的长度。
四,简单的参考代码
1,服务端
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#define LISTEN_PORT 8889
#define BACKLOG 13
#define BUFFER 1024
int main(int argc,char *argv[])
{
int listen_fd,client_fd = -1;
struct sockaddr_in serv_addr;
struct sockaddr_in clie_addr;
socklen_t clieaddr_len;
char buf[BUFFER];
int rv = -1;
//socket 过程,创立socket的fd并且给对应的协议进行通信。
if ( (listen_fd = socket(AF_INET,SOCK_STREAM,0))<0 )
{
printf("creat listen_fd failure:%s\n",strerror(errno));
return -1;
}
printf("listen_fd:%d\n",listen_fd);
//bind过程,绑定对应监听的IP地址以及端口,就是服务器端的IP从哪里接受消息。
//首先我们先定义一个sockaddr_in(IPV4)的变量serv_addr,然后进行赋值。
memset(&serv_addr,0,sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(LISTEN_PORT);//ps:注意大小端字节序。
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);//INADDR_ANY泛指本机所有的IP地址(因为你可能有好几块网卡,不同的IP)。
//然后我们进行bind绑定。
if (bind(listen_fd,(struct sockaddr*)&serv_addr,sizeof(serv_addr)))
{
printf("bind socket failture:%s\n",strerror(errno));
return -2;
}
printf("socket[%d] bind on port:[%d]\n",listen_fd,LISTEN_PORT);
//listen()过程,将socket()变成被动接受。
listen(listen_fd,BACKLOG);
while(1)
{
//开始accept创建一个新的文件描述符client——fd来进行通信。
printf("Start waiting and accept client connect\n");
memset(&clie_addr,0,sizeof(clie_addr));
//clieaddr_len = sizeof(clie_addr);//初始化len长度,accept的第三个参数必须是正的或者NULL,不使用可能产生Invalid argument错误。
if ( (client_fd = accept(listen_fd,(struct sockaddr*)&clie_addr,&clieaddr_len)) < 0 )//accept被链接,相应时,会将客户端的地址,端口记录在clie——addr中,所以不用赋值
{
printf("accept() creat client_fd failure:%s\n",strerror(errno));
return -3;
}
printf("Accept new client[%s:%d] with fd [%d]\n", inet_ntoa(clie_addr.sin_addr),ntohs(clie_addr.sin_port), client_fd);
memset(buf,0,sizeof(buf));
if ( (rv = read(client_fd,buf,sizeof(buf))) < 0 )//read和accept在这里面都是一个阻塞的过程,为了防止他读取失败后又去读取,不停读取
{ //我们这里使用continue去阻止他发生阻塞
printf("read client_fd error:%s\n",strerror(errno));
close(client_fd);
continue;
}
else if (rv == 0)
{
printf("client[%d] disconnect:\n",client_fd);
close(client_fd);
continue;
}
if ( write(client_fd,buf,rv) < 0 )
{
printf("write data back to client failure:%s\n",strerror(errno));
close(client_fd);
}
sleep(1);
close(client_fd);
}
close(listen_fd);
}
2,客户端
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define MSG_STR "Hello socket success!!!"
#define BUFFER 1024
#define SERVER_PORT 8889
#define DEF_IP "127.0.0.1"
int main(int argc,char *argv[])
{
int rv = -1;
int conn_fd = -1;
struct sockaddr_in serv_addr;
char buf[BUFFER];
char *ser_ip;
ser_ip = DEF_IP;//DEF_IP本身就是字符串,是指针(即地址哦)所以不用担心类型转换
//建立主动的socket文件描述符。
if( (conn_fd = socket(AF_INET,SOCK_STREAM,0)) < 0 )
{
printf("conn_fd socket failture:%s\n",strerror(errno));
return -1;
}
printf("creat conn_fd[%d] success\n",conn_fd);
//首先我们初始化地址端口的参数。
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERVER_PORT);
inet_aton( ser_ip,&serv_addr.sin_addr );//inet_aton的两个参数都是指针,在函数内定义,不使用define可以用*var=“127.0.0.1”然后使用这个*var,
//并且函数第二个参数使用的是sin。addr不用再往里面s_addr走
//进行connect()链接服务器,后面参数是服务器的IP和port。
if( connect(conn_fd,(struct sockaddr*)&serv_addr,sizeof(serv_addr)) < 0 )
{
printf("funtion connect failure:%s\n",strerror(errno));
return -2;
}
printf("connect success\n");
//向服务器发送请求
if( rv = write(conn_fd,MSG_STR,strlen(MSG_STR)) < 0 )
{
printf("write failure:%s",strerror(errno));
goto cleanup;
}
//接收服务器的回应并且存入buf中
memset(buf,0,sizeof(buf));
if( (rv = read(conn_fd,buf,sizeof(buf))) < 0 )//注意括号的个数,因为<优先级比=高。吃一堑长一智
{
printf("read failure:%s",strerror(errno));
goto cleanup;
}
else if( rv == 0 )
{
printf("read disconnect");
goto cleanup;
}
printf("socket success!!!:[%dbytes],[%s]\n",rv,buf);
cleanup:
close(conn_fd);
return 0;
}