首先要明白,三元组可以标识唯一主机的唯一应用程序,网络中进程的通信就可以利用三元组的标志与其他进程之间进行交互。三元组是:ip地址、端口号和连接(通讯链路)。或者也可以说是五元组:(协议,本地地址,本地端口号,远地地址,远地端口号)
socket编程建立连接的流程如下:
1.什么是socket
网络中的进程是利用socket来进行通信的。socket对于linux/Unix来说是一个特殊的文件,一些socket函数会进行读写io、关闭、打开等功能
2.socket的基本操作
2.1 socket()函数
int socket (int domain, int type, int protocol)
需要注意的是上面的type和protocol并不能随意组合,如SOCK_STREAM不可以跟IPPROTO_UDP组合
2.2 blind()函数
bind()函数会把一个地址族中的特定地址赋给socket,例如对应AF_INET,AF_INET6,就是把一个ipv4或ipv6的端口号付给socket
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
函数的三个参数:
addr是一个指针,指向要绑定给sockfd的协议地址。这个地址结构根据地址创建socket时的地址协议族的不同而不同,如ipv4的是:
struct sockaddr_in {sa_family_t sin_family; /* address family: AF_INET */in_port_t sin_port; /* port in network byte order */struct in_addr sin_addr; /* internet address */
};/* Internet address. */
struct in_addr {uint32_t s_addr; /* address in network byte order */
};
需要注意的是,在将一个地址转移到socket中时,需要先将主机字节序转换为网络字节序,而不要假定主机字节序跟网络字节序一样使用的是Big-Endian。
2.3 listen()函数
作为要给服务器,在调用socket(),bind()之后,就会调用listen()函数来监听这个socket,如果客户端这时候调用connect()发出连接请求,服务器端就会接收到这个请求。
int listen(int sockfd, int backlog);
2.4 connect()函数
客户端通过调用connect函数来建立与tcp服务器的连接
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
2.5 accept()函数
tcp服务器端依次调用socket(),bind(),listen()之后,就会监听指定的socket地址了。tcp客户端一次调用socket(),connet()后就向tcp服务器端发送了一个连接请求,TCP服务器监听到这个请求之后,就会调用accept()函数取接收请求,这样连接就建立好了。之后就可以进行网络I/O操作了。
int accept(int sockfd, struct sockaddr *addr,socklen_t *addrlen);
2.6 read(),write()等函数
连接已经建立好了,可以调用网络I/O进行读写了,即使实现了网络中不同进程之间的通信,网络I/O有以下几组:
• read()/write()
• recv()/send()
• readv()/writev()
• recvmsg()/sendmsg()
• recvfrom()/sendto()
函数声明如下:
#include <unistd.h>ssize_t read(int fd, void *buf, size_t count);ssize_t write(int fd, const void *buf, size_t count);#include <sys/types.h>#include <sys/socket.h>ssize_t send(int sockfd, const void *buf, size_t len, int flags);ssize_t recv(int sockfd, void *buf, size_t len, int flags);ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
read负责从fd中读取内容,读取成功时,read返回实际所读的字节数,如果返回值为0则表明已经读到文件结尾了,小于0表示出现了错误,如果错误为enter说明是由中断引起的。如果是ECONNREST表示网络连接出了问题。
write函数将buf中的nbytes字节内容写入文件描述符fd,成功时返回写的字节数。失败时返回-1,。在网络程序中,当我们向套接字描述符写时有两种可能
• write的返回值大于0,表示写了部分或者是全部的数据。
• 返回值小于0,说明出现了错误
其他的函数也类似
2.7 close()函数
在服务器和客户端建立连接之后,会进行一些读写操作,完成了读写操作就要关闭相应的socket描述字。
int close(int fd);
close一个TCP socket时把该socket标记为关闭,然后立即返回到调用进程。该描述字不能再由调用进程使用,也就是说不能再作为read或write的第一个参数。
注意:close操作只是使相应socket描述字的引用计数-1,只有当引用计数为0,才会触发TCP客户端向服务器端发送终止连接请求。
2.8 inet_ntop函数与ipnet_pton函数
函数中的p和n分别代表表达(presentation)和数值(numeric),二者适用于IPv4及IPv6。地址的表达格式通常为ASCII码字符串,如常用的210.26.48.46,数值格式则是存放到套接字地址结构的二进制值。
函数声明如下:
#include <arpe/inet.h>
//将点分十进制的ip地址转化为用于网络传输的数值格式
int inet_pton(int family, const char *strptr, void *addrptr);
// 返回值:若成功则为1,若输入不是有效的表达式则为0,若出错则为-1//将数值格式转化为点分十进制的ip地址格式
const char * inet_ntop(int family, const void *addrptr, char *strptr, size_t len);
//返回值:若成功则为指向结构的指针,若出错则为NULL
2.9 fread函数(读取文件)
从输入文件(数据流)中读取size*count字节 存放到buffer中,并返回内容数量
函数原型:
size_t fread( void *buffer, size_t size, size_t count, FILE *stream )
举个栗子:从fp里读取100个字节,可用以下语句:
fread(buffer,1,100,fp);
fread(buffer,100,1,fp);
fread(buffer,10,10,fp);
3 试验结果
发送端:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>#define MAXLINE 4096int main(int argc, char** argv)
{int sockfd,n;char recvline[4096],sendline[4096];struct sockaddr_in servaddr;#如果命令行输入参数不为2,就指示用法if( argc != 2){printf("usage:./client <ipaddres>\n");exit(0);}#对sockfd进行赋值,同时判断是否出错if ( (sockfd = socket(AF_INET,SOCK_STREAM,0)) < 0) {printf("create socket error:%s(errno:%d)\n",strerror(errno),errno);exit(0);}#memset(void *s,int ch,size_tn) 对servaddr地址段内容清零memset(&servaddr,0,sizeof(servaddr));#socket赋值,协议、端口servaddr.sin_family = AF_INET;servaddr.sin_port = htons(6666);#赋IP,将点分文本的IP地址转换为二进制网络字节序”的IP地址if(inet_pton(AF_INET,argv[1], &servaddr.sin_addr)<=0){printf("inet_pton error for %s\n",argv[1]);exit(0);}#建立连接if (connect (sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0){printf("conect error:%s(error:%d)\n",strerror(errno),errno);exit(0);}printf("send msg to server:\n");#fgets(str,n,stream)从stream中最多读取n个字符放到str中fgets(sendline,4096,stdin);#send(sockfd,buf,size,flags)若返回值小于0,出错if (send(sockfd,sendline,strlen(sendline),0)<0){printf("send msg error:%s(error:%d)\n",strerror(errno),errno);exit(0);}close(sockfd);exit(0);
}
接收端:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>#define MAXLINE 4096int main(int argc, char** argv)
{int listenfd, connfd;struct sockaddr_in servaddr;char buff[4096];int n;if( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1 ){printf("create socket error: %s(errno: %d)\n",strerror(errno),errno);exit(0);}memset(&servaddr, 0, sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY);servaddr.sin_port = htons(6666);if( bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1){printf("bind socket error: %s(errno: %d)\n",strerror(errno),errno);exit(0);}if( listen(listenfd, 10) == -1){printf("listen socket error: %s(errno: %d)\n",strerror(errno),errno);exit(0);}printf("======waiting for client's request======\n");while(1){if( (connfd = accept(listenfd, (struct sockaddr*)NULL, NULL)) == -1){printf("accept socket error: %s(errno: %d)",strerror(errno),errno);continue;}n = recv(connfd, buff, MAXLINE, 0);buff[n] = '\0';printf("recv msg from client: %s\n", buff);close(connfd);}close(listenfd);
}