1. API
#include <sys/types.h>
#include <sys/socket.h>
/************************************************************************
* 常用socket结构体
***********************************************************************/
// 通用函数类型:
struct sockaddr{
sa_family_t sa_family;
char sa_data[14];
}
//如ipv4对应的是:
struct sockaddr_in {
sa_family_t sin_family; /* address family: AF_INET */
in_port_t sin_port; /* port in network byte order 2字节*/
struct in_addr sin_addr; /* internet address 4字节*/
unsigned char sin_zero[8];
};
//Internet address
struct in_addr {
uint32_t s_addr; /* address in network byte order */
};
//ipv6对应的是:
struct sockaddr_in6 {
sa_family_t sin6_family; /* AF_INET6 */
in_port_t sin6_port; /* port number */
uint32_t sin6_flowinfo; /* IPv6 flow information */
struct in6_addr sin6_addr; /* IPv6 address */
uint32_t sin6_scope_id; /* Scope ID (new in 2.4) */
};
struct in6_addr {
unsigned char s6_addr[16]; /* IPv6 address */
};
//Unix域对应的是:
#define UNIX_PATH_MAX 108
struct sockaddr_un {
sa_family_t sun_family; /* AF_UNIX */
char sun_path[UNIX_PATH_MAX]; /* pathname */
};
/************************************************************************
* 工具方法
***********************************************************************/
//将字符串形式的IP地址转化为整数型的IP地址(网络字节序)
int_addr_t indet_addr(const char *cp);
//将整数形式的IP地址转化为字符串形式的IP地址
char *inet_ntoa(struct in_addr);
//网络字节序定义:收到的第一个字节被当作高位看待,这就要求发送端发送的第一个字节应当是高位。
//而在发送端发送数据时,发送的第一个字节是该数字在内存中起始地址对应的字节。
//可见多字节数值在发送前,在内存中数值应该以大端法存放。
//网络字节序说是大端字节序。
//将32位的数据从主机字节序转换为网络字节序
uint32_t htonl(uint32_t hostlong);
//将16位的数据从主机字节序转换为网络字节序
uint16_t htons(uint16_t hostshort);
//将32位的数据从网络字节序转换为主机字节序
uint32_t ntohl(uint32_t netlong);
//将16位的数据从网络字节序转换为主机字节序
uint16_t ntohs(uint16_t netshort);
/************************************************************************
* API
***********************************************************************/
/**
* 创建socket
*
* @param domain IPv4直接填写AF_INET
* @param type TCP使用SOCK_STEAM, UDP使用SOCK_DGRAM
* @param protocol 填0即可
* @return 成功返回socket句柄,失败返回-1
**/
int socket(int domain,int type,int protocol);
/**
* 一旦你有了一个套接口以后,下一步就是把套接口绑定到本地计算机的某一个端口上。
* 但如果你只想使用connect()则无此必要。
*
* @param addrlen sizeof(struct sockaddr)
* @return 失败返回-1
**/
int bind(int sockfd, struct sockaddr *my_addr, int addrlen);
/**
* 绑定端口后就可以开启监听了
* @param backlog 进入队列中允许的连接的个数。
* 进入的连接请求在使用系统调用accept()应答之前要在进入队列中等待。
* 这个值是队列中最多可以拥有的请求的个数。大多数系统的缺省设置为20。你可以设置为5或者10。
* @return 失败返回-1
**/
int listen(int sockfd,int backlog);
/**
* 如果你使用connect()系统调用,那么你不必知道你使用的端口号。
* 当你调用connect()时,它检查套接口是否已经绑定,如果没有,它将会分配一个空闲的端口。
*
* @param addrlen sizeof(struct sockaddr)
* @return 成功返回0,失败返回-1
**/
int connect(int sockfd,struct sockaddr *serv_addr, int addrlen);
/**
* 服务端阻塞监听客户端连接
*
* @return 成功返回连接句柄,出错返回-1
**/
int accept(int sockfd,void *addr,int *addrlen);
/**
* 发送数据,服务端发送接收数据使用accept()返回的句柄
*
* @param msg 发送数据的指针
* @param len 发送数据长度
* @param flags 设0
* @return 返回实际发送的数据长度,如果没法送完要接着下次发送
**/
int send(int sockfd,const void* msg,int len,int flags);
/**
* 接收数据,服务端接收接收数据使用accept()返回的句柄
*
* @param len 接收最大长度
* @return 返回实际接收长度,出错返回-1
**/
int recv(int sockfd,void* buf,int len,unsigned int flags);
/**
* 发送数据,除了两个参数以外,其他的参数和系统调用send()时相同。
*
* @param to 包含目的IP地址和端口号的数据结构sockaddr的指针
* @return 同send()
**/
int sendto(int sockfd, const void* msg, int len, unsigned int flags,
const struct sockaddr *to, int tolen);
/**
* 接收数据,除了两个参数以外,其他的参数和系统调用recv()时相同
*
* @param from 包含源IP地址和端口号的数据结构sockaddr的指针
* @return 同recv()
**/
int recvfrom(int sockfd,void* buf, int len, unsigned int flags,
struct sockaddr* from,int* fromlen);
/**
* 关闭连接
**/
int close(int sockfd);
/**
* 使用系统调用shutdown(),可有更多的控制权。它允许你在某一个方向切断通信,或者切断双方的通信
*
* @param how 0,1,2
* @return 成功返回0,失败返回-1
**/
int shutdown(int sockfd,int how);
/**
* 返回对方ip
**/
int getpeername(int sockfd,struct sockaddr *addr,int *addrlen);
/**
* 返回当前计算机名字
*
* @return 成功返回0,失败返回-1
**/
int gethostname(char *hostname,size_t size);
2. 建立连接
服务端
服务器端先初始化socket,然后与端口绑定,对端口进行监听,调用accept阻塞,等待客户端连接。
socket() -> bind() -> listen() -> accept()
客户端
客户端先初始化socket,然后与服务端连接,服务端监听成功则连接建立完成
socket() -> connect()
3. 示例
服务端
/* File Name: server.c */
#include<stdio.h>
#include<stdlib.h>
#include<cstring>
#include<errno.h>
#include<sys/types.h>
#include<sys/socket.h>
#define DEFAULT_PORT 8000
#define MAXLINE 4096
int main(int argc, char** argv)
{
int socket_fd, connect_fd;
struct sockaddr_in servaddr;
char buff[4096];
int n;
//初始化Socket
if( (socket_fd = 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);//IP地址设置成INADDR_ANY,让系统自动获取本机的IP地址。
servaddr.sin_port = htons(DEFAULT_PORT);//设置的端口为DEFAULT_PORT
//将本地地址绑定到所创建的套接字上
if( bind(socket_fd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1){
printf("bind socket error: %s(errno: %d)\n",strerror(errno),errno);
exit(0);
}
//开始监听是否有客户端连接
if( listen(socket_fd, 10) == -1){
printf("listen socket error: %s(errno: %d)\n",strerror(errno),errno);
exit(0);
}
printf("======waiting for client's request======\n");
while(1){
//阻塞直到有客户端连接,不然多浪费CPU资源。
if( (connect_fd = accept(socket_fd, (struct sockaddr*)NULL, NULL)) == -1){
printf("accept socket error: %s(errno: %d)",strerror(errno),errno);
continue;
}
//接受客户端传过来的数据
n = recv(connect_fd, buff, MAXLINE, 0);
//向客户端发送回应数据
if(!fork()){//子进程进入if
if(send(connect_fd, "Hello,you are connected!\n", 26,0) == -1)
perror("send error");
close(connect_fd);
exit(0);//退出子进程
}
buff[n] = '\0';
printf("recv msg from client: %s\n", buff);
close(connect_fd);
}
close(socket_fd);
}
客户端
/* File Name: client.c */
#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 4096
int main(int argc, char** argv)
{
int sockfd, n,rec_len;
char recvline[4096], sendline[4096];
char buf[MAXLINE];
struct sockaddr_in servaddr;
if( argc != 2){
printf("usage: ./client <ipaddress>\n");
exit(0);
}
if( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
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_port = htons(8000);
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("connect error: %s(errno: %d)\n",strerror(errno),errno);
exit(0);
}
printf("send msg to server: \n");
fgets(sendline, 4096, stdin);
if( send(sockfd, sendline, strlen(sendline), 0) < 0)
{
printf("send msg error: %s(errno: %d)\n", strerror(errno), errno);
exit(0);
}
if((rec_len = recv(sockfd, buf, MAXLINE,0)) == -1) {
perror("recv error");
exit(1);
}
buf[rec_len] = '\0';
printf("Received : %s ",buf);
close(sockfd);
exit(0);
}