前言: Socket通信是基于TCP/IP协议的通信。在工作和做项目中应用非常广,下面来介绍下Socket网络编程!
Socket的介绍
首先,在Socket网络编程中我们要了解两个重要的东西,ip和端口号,一台拥有IP地址的主机可以提供许多服务,比如Web服务、FTP服务、SMTP服务等。这些服务完全可以通过1个ip地址来实现。主机是怎样区分不同的网络服务的?显然不能只靠ip地址,因为ip地址与网络服务的关系是一对多的关系。 实际上是通过“ip地址+端口号”来区分不同的服务的。一般端口号在5000~10000。
1.字节序
字节序:指多字节数据在计算机内存中存储或者网络传输时各字节的存储顺序。 Little endian(小端字节序):将低序字节存储在起始地址。 Big endian(大端字节序):将高序字节存储在起始地址。
举个例子:int a = 0x12345678; 0x78属于低地址,而0x12属于高地址 如果是大端模式,那输出方式是0x12 0x34 0x56 0x78,如果是小端模式,那么就是0x78 0x56 0x34 0x12
这个很容易理解,人类读写数据的习惯是大端字节序,而小端字节序是反着人类来的,而我们网络字节序传输是大端模式,因此要转为大端模式。
2.字节序转换api:
#include <netinet/in.h>
uint16_t htons(uint16_t host16bitvalue);
htonl(uint32_t host32bitvalue);
net16bitvalue);
h代表host,n代表net,s代表short(两个字节),l代表long(4个字节),通过上面的4个函数可以实现主机字节序和网络字节序之间的转换。有时可以用INADDR_ANY,INADDR_ANY指定地址让操作系统自己获取
Socket的开发步骤
Socket服务器和客户端的开发步骤:
Linux提供的API简析
1.socket(创建套接字)
int socket(int domain, int type, int protocol);
2.bind()函数
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
功能:用于绑定ip地址跟端口号到socketfd。
参数说明:
sockfd--是一个文件描述符
addr:是一个指向包含有本地IP及端口号等信息等信息的sockaddr类型的指针,指向要绑定给socket的协议地址结构,这个地址结构根据地址创建socket时的地址协议族的不同而不同。
struct sockaddr_in结构体:
struct sockaddr_in
{
sa_family_t sin_family;
in_addr sin_addr;
unsigned char __pad[__SOCK_SIZE__ -sizeof(short int) - sizeof(unsigned short int) - sizeof(struct in_addr)];
};
struct in_addr
{
__be32 s_addr;
};
bind函数绑定实例:
此结构体因为成员中设置ip和端口的变量时分开的,所以设置方便,但是bind函数要求的是struct sockaddr类型的结构体变量,所以使用struct sockaddr_in设置以后要将其强制转化为struct sockaddr类型,然后传值给bind函数。
struct sockaddr_in addr;
addr.sin_family = AF_INET;
htons(5006);
inet_addr("192.168.1.10");
sockaddr*)&addr,sizeof(sddr));
htons函数
功能:将一个无符号短整型数值转换为网络字节序,即大端模式(big-endian);
#include <arpa/inet.h>
uint16_t htons(uint16_t hostshort);
addr.sin_port = htons(5006);
inet_addr函数
功能:将一个字符串格式的ip地址转换成一个uint32_t数字格式。
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
in_addr_t inet_addr(const char *cp);
eg:
addr.sin_addr.s_addr = inet_addr("192.168.1.10");
地址转换API
int inet_aton(const char* straddr,struct in_addr *addrp);
把字符串形式的“192.168.1.123”转为网络能识别的格式
char* inet_ntoa(struct in_addr inaddr); 把网络格式的ip地址转为字符串形式
3.listen()函数
#include <sys/types.h>
#include <sys/socket.h>
int listen(int sockfd, int backlog);
功能:监听设置函数
参数说明:
sockfd:文件描述符
backlog:指定在请求队列中允许的最大请求数
4.accept()函数
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t*addrlen);
功能:accept函数由TCP服务器调用,用于从已完成连接队列队头返回下一个已完成连接,如果已完成连接队列为空,那么进程被投入睡眠。
参数说明:
sockfd:文件描述符
addr:用来返回已连接的对端(客户端)的协议地址
addrled:客户端地址长度
返回值:accept函数返回值成功时返回非负值,失败时返回-1
5.1. 数据收发(recv 、send)
函数原型:ssize_t send(int sockfd, const void *buf, size_t len, int flags);
功能:向套接字中发送数据
参数:
sockfd:向套接字中发送数据
buf:要发送的数据的首地址
len:要发送的数据的字节
int flags:设置为MSG_DONTWAITMSG 时 表示非阻塞 设置为0时 功能和write一样
返回值:成功返回实际发送的字节数,失败返回 -1
函数原型:ssize_t recv(int sockfd, const void *buf, size_t len, int flags);
功能:向套接字中发送数据
参数:
sockfd:在哪个套接字接
buf:存放要接收的数据的首地址
len:要接收的数据的字节
int flags:设置为MSG_DONTWAITMSG 时 表示非阻塞设置为0时 功能和read一样
返回值:成功返回实际发送的字节数,失败返回 -1
5.2. 数据收发(read 、write)
#include <unistd.h>
ssize_t read(int fd, void * buf, size_t nbytes);
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t nbytes);
6.客户端的connect函数
#include <sys/types.h>
#include <sys/socket.h> int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数说明:
sockfd: socket文件描述符
addr: 传入参数,指定服务器端地址信息,含IP地址和端口号
addrlen: 传入参数,传入sizeof(addr)大小
返回值:成功返回0,失败返回-1,设置errno
7. 一个客户端与服务器连接的demo
服务器:
#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>
int main(int argc,char **argv)
{
int s_fd;
int c_fd;
int ret;
int n_read;
char readBuf[128];
char msg[128]={0};
int mark=0;
s_fd = socket(AF_INET,SOCK_STREAM ,0);
if(s_fd == -1){
perror("socket");
exit(-1);
}
if(argc!=3){
printf("pamrm error\n");
exit(-1);
}
struct sockaddr_in a_addr;
struct sockaddr_in c_addr;
memset(&a_addr,0,sizeof(struct sockaddr_in));
memset(&c_addr,0,sizeof(struct sockaddr_in));
a_addr.sin_family=AF_INET;
a_addr.sin_port=htons(atoi(argv[2]));
a_addr.sin_addr.s_addr=inet_addr(argv[1]);
ret = bind(s_fd,(struct sockaddr*)&a_addr,sizeof(struct sockaddr_in));
if(ret==-1){
perror("bind");
exit(-1);
}
int len=sizeof(struct sockaddr_in);
listen(s_fd,10);
while(1){
c_fd = accept(s_fd,(struct sockaddr*)&c_addr,&len);
mark++;
printf("get connect: %s\n",inet_ntoa(c_addr.sin_addr));
if(fork()==0){
if(fork()==0){
while(1){
sprintf(msg,"Welcome to No%d client\n",mark);
write(c_fd,msg,strlen(msg));
sleep(3);
}
}
while(1){
memset(readBuf,0,sizeof(readBuf));
n_read=read(c_fd,readBuf,128);
if(n_read==-1){
perror("read");
}else{
printf("get:%s\n",readBuf);
}
}
break;
}
}
return 0;
}
客户端:
include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>
int main(int argc,char **argv)
{
int c_fd;
int ret;
int n_read;
char msg[128]={0};
char readBuf[128]={0};
c_fd=socket(AF_INET,SOCK_STREAM,0);
if(c_fd==-1){
perror("socket");
exit(-1);
}
struct sockaddr_in addr;
addr.sin_family=AF_INET;
addr.sin_port=htons(atoi(argv[2]));
addr.sin_addr.s_addr=inet_addr(argv[1]);
ret=connect(c_fd,(struct sockaddr*)&addr,sizeof(struct sockaddr_in));
if(ret==-1){
perror("connect");
exit(-1);
}
while(1){
if(fork()==0){
while(1){
memset(msg,0,sizeof(msg));
printf("input ");
gets(msg);
write(c_fd,msg,sizeof(msg));
}
}
while(1){
memset(readBuf,0,sizeof(readBuf));
n_read=read(c_fd,readBuf,sizeof(readBuf));
if(n_read==-1){
perror("read");
}else{
printf("get:%s\n",readBuf);
}
}
}
return 0;
}
|