Socket可以看成是用户进程与内核网络协议栈的接口可以把socket看作进程间通信的一种方式,它是全双工的通信方式, 其不仅可以用于本机进程间通信,可以用于网络上不同主机的进程间通信, 甚至还可以用于异构系统之间的通信。
字节序
1.大端字节序(Big Endian)
最高有效位存储于最低内存地址处,最低有效位存储于最高内存地址处。
2.小端字节序(Little Endian)
最高有效位存储于最高内存地址处,最低有效位存储于最低内存地址处。
3.主机字节序
不同的主机有不同的字节序,如x86为小端字节序。
4.网络字节序
网络字节序规定为大端字节序
在编程时,IP地址和端口号在网络传输是使用的网络字节序,而字符串是在主机字节序,所以在传递之前需要用到以下函数转换。
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
int inet_aton(const char *cp, struct in_addr *inp);
char *inet_ntoa(struct in_addr in);
要给端口号赋值时可以用这几个函数
htonl(将32位主机字符顺序转换成网络字符顺序)
返回值:返回对应的32位网络字符顺序。
htons(将16位主机字符顺序转换成网络字符顺序)
返回值:返回对应的16位网络字符顺序。
ntohl (将32位网络字符顺序转换成主机字符顺序)
返回值:返回对应的32位主机字符顺序
ntohs (将16位网络字符顺序转换成主机字符顺序)
返回值:返回对应的16位主机字符顺序
要给IP地址赋值时可以用这几个函数
inet_aton (将主机序列转换为网络字节序列)
cp是格式为主机号序列的ip地址 inp是存放网络字节序列的结构体
inet_ntoa(将网络字节序列转换成主机字节序列)
in是存放网络字节序列的结构体 返回值:主机序列的IP地址
API
服务端
#include <sys/types.h>
#include <sys/socket.h>
struct sockaddr_in {
__kernel_sa_family_t sin_family;
__be16 sin_port;
struct in_addr sin_addr;
};
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
}
struct in_addr {
__be32 s_addr;
};
int socket(int domain, int type, int protocol);
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
int listen(int sockfd, int backlog);
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
socket
socket是为了获得套接字的函数
domain:指定通信协议族,一般取值AF_INET,ipv4协议族 type :指定socket类型, 流式套接字SOCK_STREAM,数据报套接字SOCK_DGRAM,原始套接字SOCK_RAW,因为用的是tcp/ip协议,这里一般会使用流式套接字 protocol:为协议类型,一般取0,会默认选择
bind
bind是和套接字绑定端口号绑定ip地址和端口号
sockfd:是服务端套接字 addr:存放IP地址和端口号的结构体,这里会使用sockaddr_in,也就是ipv4套接字地址结构,所以在传递参数时需要进行强制转换,想单独寻找sockaddr_in的原型的话,可以在/usr/include下使用如下指令
grep "sockaddr_in{" * -nir n显示行号,i不区分大小写,r递归查找
addrlen:传入的结构体的大小
listen
listen监听客户端的接入
sockfd:是服务端套接字 backlog:是已经发出信号给服务端,并等待完成三次握手的过程中的客户端和已经完成连接的客户端,这两个队列之和的最大值
accept
accept接受到服务端的连接
sockfd:服务器套接字 addr:客户的套接字地址, 不关心的话, 可以设置为NULL addrlen:客户的套接字地址长度, 不关心的话可以设置成为NULL, 否则一定要初始化 返回值为客户端的套接字,如果没有连接上客户端将会一直阻塞,accept4多了一个flag参数,当该参数设置为0时,效果是和accept一样,但是可以设置为 SOCK_NONBLOCK,也就是非阻塞的形式,所以zuihao还是选用accept,因为服务端的建立就是为了收到客户端的请求的
客户端
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
socket:获得客户端的套接字,格式和服务端一样。
connect
connect:尝试获得与服务器的连接
sockfd:服务端的套接字 addr:存放IP地址和端口号的结构体指针,这里的是目标服务器的IP地址和端口号 addrlen:传入结构体的大小
当服务器和客户端建立的连接后,给对方发送数据就可以用read和write函数发送了
这里综合以上函数写的例子,效果是客户端和服务端可以互相收发消息
服务端
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <arpa/inet.h>
int main(int argc,char **argv){
int s_fd;
int c_fd;
int size;
int pid;
struct sockaddr_in s_addr;
struct sockaddr_in c_addr;
char readbuf[128]={0};
char writebuf[128]={0};
memset(&s_addr,0,sizeof(struct sockaddr_in));
memset(&c_addr,0,sizeof(struct sockaddr_in));
size=sizeof(c_addr);
s_fd=socket(AF_INET,SOCK_STREAM,0);
if(s_fd==-1){
perror("socket");
exit(-1);
}
s_addr.sin_family=AF_INET;
s_addr.sin_port=htons(atoi(argv[2]));
inet_aton(argv[1],&s_addr.sin_addr);
if(bind(s_fd,(struct sockaddr *)&s_addr,sizeof(s_addr))){
perror("bind");
exit(-1);
}
listen(s_fd,10);
while(1){
c_fd=accept(s_fd,(struct sockaddr *)&c_addr,&size);
if(c_fd==-1){
perror("accept");
exit(-1);
}
pid=fork();
if(pid==0){
while(1){
memset(writebuf,0,strlen(writebuf));
printf("请输入消息:\n");
gets(writebuf);
write(c_fd,writebuf,strlen(writebuf));
printf("服务端子:%d\n",getpid());
}
}
else if(pid>0){
while(1)
{
printf("客户端的ip地址是:%s\n",inet_ntoa(c_addr.sin_addr));
read(c_fd,readbuf,sizeof(readbuf));
printf("接受的消息是:%s\n",readbuf);
memset(readbuf,0,sizeof(readbuf));
printf("服务端父:%d\n",getpid());
}
}
}
return 0;
}
客户端
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <arpa/inet.h>
int main(int argc,char **argv){
int c_fd;
int size;
struct sockaddr_in c_addr;
char readbuf[128];
char writebuf[128];
int pid;
memset(&c_addr,0,sizeof(struct sockaddr_in));
c_fd=socket(AF_INET,SOCK_STREAM,0);
if(c_fd==-1){
perror("socket");
exit(-1);
}
c_addr.sin_family=AF_INET;
c_addr.sin_port=htons(atoi(argv[2]));
inet_aton(argv[1],&c_addr.sin_addr);
if(connect(c_fd,(struct sockaddr *)&c_addr,sizeof(c_addr))==-1){
perror("connect");
exit(-1);
}
pid=fork();
if(pid==0){
while(1){
sleep(1);
read(c_fd,readbuf,sizeof(readbuf));
printf("收到的消息是:%s\n",readbuf);
printf("客户端子:%d\n",getpid());
memset(readbuf,0,sizeof(readbuf));
}
}
else if(pid>0){
while(1){
memset(writebuf,0,sizeof(writebuf));
printf("请输入消息:\n");
gets(writebuf);
write(c_fd,writebuf,strlen(writebuf));
printf("客户端父:%d\n",getpid());
}
}
return 0;
}
在写客户端之前如果要先测试服务端是否能正常运行,可以用windows自带的telnet来测试
|